Connecting to the Avid Platform using the Avid Connector API

Connecting to the Avid Platform using the Avid Connector API

The Avid Connector API connects to the Avid Platform over the Avid Secure Gateway. To connect your service to Avid Platform, the Avid Secure Gateway must be running on known host and port. The default port is 9900.

Default Connection Settings

To connect using default connection settings, create a BusAccess object and call the connect method.

1
2
3
4
5
6
7
boost::shared_ptr<acs::BusAccess> bus(ACSCreateBusAccess0(), ACSDestroyBusAccess);

ACSResult result = bus->connect();
if (result) {
std::cerr << "Failed to connect to the broker (" << ACSErrorMessage(result) << ")";
return -1;
}

The default settings are:

  • Query Timeout (ms): 10,000
  • Gateway Port: 9900
  • Gateway Host: 127.0.0.1
  • Bus Initial Connection Attempts: -1 (try forever)
  • Bus Initial Reconnection Attempts: -1 (try forever)

Non-default settings can be set by passing a ConnectionInfo object to the BusAccess constructor (ACSCreateBusAccess7):

1
2
3
4
boost::shared_ptr<acs::ConnectionInfo> connectioninfo(ACSCreateConnectionInfo0(), ACSDestroyConnectionInfo);
connectioninfo->setPort(9900);

boost::shared_ptr<acs::BusAccess> bus(ACSCreateBusAccess7(connectioninfo.get()), ACSDestroyBusAccess);

Connection settings can also be specified via environment variables prior to calling ACSCreateBusAccess0:
vv

Environment Variable Description
ACS_BUS_QUERY_TIMEOUT The default timeout (in ms) for Avid Platform queries. Default is 10000 ms
ACS_GATEWAY_HOST Secure Gateway connection host. Default is 127.0.0.1
ACS_GATEWAY_PORT Secure Gateway connection port. Default is 9900
ACS_GATEWAY_UNSECURE_PORT Port for unsecured connection. Default is 9966
ACS_GATEWAY_PROTOCOL_SEQUENCE Sequence of protocols (or just one protocol) separated by coma ‘,’ in which Avid Connector API will try to establish connection to Secure Gateway. Default is ‘wss’. Allowed protocols are ‘wss’ and ‘ws’. Possible combinations are ‘wss,ws’, ‘ws’, ‘wss’ or ‘ws,wss’.
ACS_PLATFORM_IDENTIFIER Unique node identifier, where service is running. Must be provided by target platform, i.e. AWS, OpenStack, etc. (Default ‘unknown’)
ACS_SERVICE_BUILD_NUMBER RPM version or any other version of the service (Default ‘unknown’)
ACS_ENVIRONMENT_IDENTIFIER Environment identifier is basically chef generalized identifier for any collection of nodes (Default ‘unknown’)
ACS_BUS_INITIAL_CONNECTION_ATTEMPTS The # of times to attempt the initial connection before failing (-1 means to try forever)
ACS_GATEWAY_CONNECTION_LOST_THRESHOLD Amount of time, after which connection to Gateway considered as broken if we didn’t get ping from gateway. (Default 5000 ms)
ACS_BUS_RECONNECTION_ATTEMPTS The # of times to attempt reconnecting a broken connection (-1 means to try forever)
ACS_BUS_RECONNECTION_DELAY Delay in ms between connection attempts to Secure Gateway. This delay same both for initial connection attempts and reconnections attempts. Default is 1000 ms.
ACS_SECURITY_TRUST_SELF_SIGNED Whether to trust (true) or not trust (false) to self signed certificates. Default is true.

When connecting to Avid Platform, authentication provider may be specified with valid clientId and client secret. If service is started from trusted IP address, it may be started w/o identity token, in this case authentication provider must not be provided:

1
2
3
4
boost::shared_ptr<acs::ConnectionInfo> connectioninfo(ACSCreateConnectionInfo0(), ACSDestroyConnectionInfo);
const acs::AuthenticationProvider* auth = ACSCreateClientAuthentication("620f8ca6-0b0e-11e6-b512-3e1d05defe78", "9e58f2e7-ad7e-4b9d-ab87-d57bdb47261f");
connectioninfo->setAuthenticationProvider(auth);
boost::shared_ptr<acs::BusAccess> bus(ACSCreateBusAccess7(connectioninfo.get()), ACSDestroyBusAccess);

Preventing Gateway Re-Connection Attempts while Debugging

The Avid Connector API and the Secure Gateway have an internal failover logic to validate the connection between them. If you are debugging your service in an IDE or on the command line, a breakpoint can block your service from receiving the information it needs to know that it is still connected to the Secure Gateway. To prevent the service from thinking it has lost its connection to the Secure Gateway, and thus failing over into reconnection mode, you should set the variable ACS_GATEWAY_CONNECTION_LOST_THRESHOLD=600000 (10 minutes). This should prevent the service from thinking it has lost connection while giving you enough time to inspect the information you need at the breakpoint.

Connection Events

If your application needs to react when a connection to the Avid Platform established/lost, pass in implementation of the acs::ConnectionEventHandler interface when creating a BusAccess object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ConnectionEvents : public acs::ConnectionEventHandler
{
public:
virtual void onConnect( )
{
// ...
}

virtual void onDisconnect( )
{
// ...
}

};

// ...

ConnectionEvents connectionHandler;

st::shared_ptr<acs::BusAccess> bus(ACSCreateBusAccess6(&connectionHandler), ACSDestroyBusAccess);

Threading Model

For processing incoming messages (e.g. service operations/requests, channel messages and incoming responses to query operations), the Avid Connector API for C++ uses a pool of threads. The size of the thread pool can be configured by setting the ACS_BUS_MAX_CONCURRENT_OPERATIONS environment variable (the default is 32).

All outgoing messages are sent on the currently executing thread.

Each instance of a acs::BusAccess has its own private thread pool.

Using the Avid Connector API as a Client

The Connector API can be used to send requests to services, and to subscribe and publish to channels. Requests and responses use the BusMessage interface.

An example of this can be found in the BusClient project, in the examples provided with the API.

Providing Message Options

With each operation (e.g. query, send, broadcast) you may provide message options, via a MessageOptions object. The following options are available:

  • Timeout - Specifies a message timeout in ms for query operations. Default is 10000ms.
  • Durable - Sets whether message is durable. Default is false. NOTE: This option is currently not implemented, and will be revised in the future releases.
  • AnyCompatibleVersion - Sets whether the message is delivered to any compatible version of the service or to an exact version of the service. Default is true.
1
2
3
4
5
MessageOptions* options = ACSCreateMessageOptions();
options->setDurable(false);
options->setTimeout(-1);
options->setAnyCompatibleVersion(true);
result = ACSResultErrorCode(MyBAL->query(requestMsg.get(), options, &responseMsg));

Querying Services

All queries to services should be performed asynchronously.

An example of asynchronous querying can found in the ExampleService project.

To execute an asynchronous query, pass an implementation of acs::AsyncQueryResult:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
class GetNumberOfSubmittedJobsResult
: public acs::ReferenceCounter<GetNumberOfSubmittedJobsResult>
, public acs::AsyncQueryResult
{
public:
typedef boost::intrusive_ptr<GetNumberOfSubmittedJobsResult> pointer;

GetNumberOfSubmittedJobsResult() :m_done(false), m_value(0), m_printed(false), m_responder(0) {
}

GetNumberOfSubmittedJobsResult(acs::OperationContext *responder) : m_done(false), m_value(0), m_printed(false){
m_responder = ACSCopyResponder(responder);
}

~GetNumberOfSubmittedJobsResult() {
ACSDestroyResponder(m_responder);
}

virtual void ACSBUS_CALL onSuccess(acs::BusMessage* response) {
std::cout << "We got a response from attributes " << std::endl;

if (response->getErrors()->size()) {
// ...
m_done = true;
return;
}
if (response->getResults()->size() == 0) {
std::cout << "No results " << std::endl;
m_responder->respond("asdf", ACSCreateVariant(11));
m_done = true;
} else {
const ACSVariant *result = response->getResults()->at(0)->value();
m_value = ACSVariant_cast<int>(result);
acs::Variant val(ACSCreateVariant(11));
m_responder->respond("numSubmittedJobs", val);
m_done = true;
}
}

virtual void ACSBUS_CALL onError(const acs::OperationError* error) {
m_error = error->message();
m_done = true;
}

virtual void ACSBUS_CALL onTimeout() {
std::cout << "TIMEOUT TIMEOUT" << std::endl;
m_done = true;
}

virtual void ACSBUS_CALL addRef() const
{
intrusive_ptr_add_ref(this);
}

virtual void ACSBUS_CALL release() const
{
intrusive_ptr_release(this);
}

private:
bool m_done;
int m_value;
bool m_printed;
std::string m_error;
acs::OperationContext *m_responder;
};

// ...

void ExampleService::getNumberOfSubmittedJobs(acs::OperationContext *responder) {
acs::BusMessageSP request(ACSCreateBusMessage2("avid.acs.attributes", "global", 3, "fetch"));
request->getParameters()->addParameter("name", acs::Variant("cpp.service.store"));

GetNumberOfSubmittedJobsResult::pointer opResult(new GetNumberOfSubmittedJobsResult(responder));

m_context->bus()->query(request.get(), opResult.get());
};

The onError method is only called when there are acs::OperationError-type errors. The acs::BusMessage passed to onSuccess() will contain any errors that occurred while executing the service operation.

Sending to Services

Sending to a service is a one-way communication from the client to the service. There is no response. In comparison to a broadcast (described below), the client is also guaranteed that no more than one service instance will process the message.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class OperationResult
: public acs::ReferenceCounter<OperationResult>
, public acs::AsyncOpResult
{
public:
typedef boost::intrusive_ptr<OperationResult> pointer;

OperationResult(const std::string& op) : m_op(op) {}

virtual void ACSBUS_CALL onSuccess()
{
std::cout << m_op << " succeeded." << std::endl;
}

virtual void ACSBUS_CALL onError(const acs::OperationError* error)
{
std::cerr << m_op << " failed: " << error->message() << std::endl;
}

virtual void ACSBUS_CALL addRef() const
{
intrusive_ptr_add_ref(this);
}

virtual void ACSBUS_CALL release() const
{
intrusive_ptr_release(this);
}

private:
const std::string m_op;
};
// ...
acs::BusMessageSP request(ACSCreateBusMessage2(m_serviceType.c_str(), m_serviceRealm.c_str(), 1, "add"), ACSDestroyBusMessage);
request->getParameters()->addParameter("num1", acs::Variant(atoi(num1.c_str())));
request->getParameters()->addParameter("num2", acs::Variant(atoi(num2.c_str())));

OperationResult::pointer opResult(new OperationResult("Send \"add\""));
ACSResult e = busClient->send(request.get(), opResult.get());

Broadcasting to Services

Broadcasting to a service is a one-way communication from the client to all instances of a given service. There is no response. As opposed to a send, the message is processed by every available instance of the service.

1
2
3
4
5
6
7
// ...
acs::BusMessageSP request(ACSCreateBusMessage2(m_serviceType.c_str(), m_serviceRealm.c_str(), 1, "add"), ACSDestroyBusMessage);
request->getParameters()->addParameter("num1", acs::Variant(atoi(num1.c_str())));
request->getParameters()->addParameter("num2", acs::Variant(atoi(num2.c_str())));

OperationResult::pointer opResult(new OperationResult("Send \"add\""));
ACSResult e = busClient->broadcast(request.get(), opResult.get());

Remote Zone and Multi-Zone Communications

The default behavior of the Avid Connector API is to communicate within its own local zone. All the examples provided above use this default behavior. If the local zone has been initialized in a multi-zone environment, however, it is possible communicate with services in other zones.

Zone-Specific Communications

To communicate with a specific remote zone, use the bus.Zone(const char* zoneId) object. The following are examples of communications with other zones:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class AsyncQueryResultImpl : public acs::AsyncQueryResult
{
public:
virtual void onSuccess(acs::BusMessage* message)
{
// do something successful
}

virtual void onError(const acs::OperationError* error)
{
// do somethign with the error
}

virtual void onTimeout()
{
// Do something for event of timeout
}

virtual void addRef() const
{
}

virtual void release() const
{
}
};

// ...
std::map <std::string, std::string> zones;
acs::BusAccessSP bus;
acs::BusAccessHostSP busAccessHost;
// ...
zones.clear();
zones["local"] = "00000000-0000-0000-0000-00000000";
zones["multizone"] = "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFF";
zones["cheech"] = "aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaa";
zones["chong"] = "bbbbbbbb-bbbb-4bbb-bbbb-bbbbbbbb";

std::string zone = "aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaa";
std::map<std::string, std::string>::iterator it = zones.find(z);
zone = it->second;

acs::BusMessageSP request(ACSCreateBusMessage2(m_serviceType.c_str(), m_serviceRealm.c_str(), 1, op.c_str()), ACSDestroyBusMessage);
acs::BusMessage* response = 0;

request->getParameters()->addParameter("num1", acs::Variant(atoi(num1.c_str())));
request->getParameters()->addParameter("num2", acs::Variant(atoi(num2.c_str())));

boost::shared_ptr<acs::AsyncQueryResult> aqr = boost::shared_ptr<acs::AsyncQueryResult>(new AsyncQueryResultImpl());

if (zone == zones.find("local")->second) {
std::cout << "local query" << std::endl;
busAccessHost.reset(bus->localZone(), ACSDestroyBusAccessHost);
} else if (zone.c_str() == zones.find("multizone")->second) {
std::cout << "multizone query" << std::endl;
busAccessHost.reset(bus->multiZone(), ACSDestroyBusAccessHost);
} else {
std::cout << "Cannot query specific zone" << std::endl;
}

ACSResult result = busAccessHost->query(request.get(), arq.get());
if (result) {
const char* errorMessage = ACSErrorMessage(result);
std::cout << "Error : " << errorMessage << std::endl;
ACSFree((void*)errorMessage);
}

Similar methods can be used to send() and broadcast() messages to multizone services. In all of the above cases, only services in the remote zone with an ID of aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaa are invoked. In addition, only service instances registered with a scope of multi-zone are considered.

Multi-Zone Communications

To communicate across multiple zones, use the bus->multiZone() object. The following are examples of multi-zone communications:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
acs::BusMessageSP request(ACSCreateBusMessage2(m_serviceType.c_str(), m_serviceRealm.c_str(), 1, op.c_str()), ACSDestroyBusMessage);
bus->multiZone().query(request, queryResult.get());
bus->multiZone().send(request, opResult.get());
bus->multiZone().broadcast(request, opResult.get());
````

Note that `bus->multiZone()->query()` and `bus->multiZone()->send()` only send to one service instance in one zone. If there is a service instance in the local zone, it sends to that one. Otherwise, it sends to a service instance in a remote zone (if one is available and registered with the `multi-zone` scope). This is particularly useful if you know the service is in a zone, but aren't sure which one

`bus.MultiZone.Broadcast` broadcasts to _all_ matching service instances in _all_ zones.

### Local Zone Communications

Note that there is also a `bus->localZone()` object. Invoking `query`, `send`, and `broadcast` on this object is functionally equivalent to invoking the same methods on the base `bus` object.

## Wildcards Usage in the Realm

If multiple realms of the same service are registered, you can use wildcards to address any service instance satisfying a wildcard expression. Wildcards can substitute for any letters/digits between the dot delimiters.

For example, consider an Avid Platform service registered with the following realms:

montreal.workgroup1;
montreal.workgroup2;
montreal.workgroup1.id1;
montreal.workgroup1.id2;
montreal.workgroup2.id1;
montreal.workgroup2.id2;

In the above case, you can address the services by supplying the following wildcarded realms in the request:

request = ACSCreateBusMessage2("example.service", "*.*", 0, "echo");
request = ACSCreateBusMessage2("example.service", "*.*.*", 0, "echo");
request = ACSCreateBusMessage2("example.service", "montreal.*", 0, "echo");
request = ACSCreateBusMessage2("example.service", "montreal.workgroup1.id*", 0, "echo");
request = ACSCreateBusMessage2("example.service", "montreal.workgroup*.id2", 0, "echo");
request = ACSCreateBusMessage2("example.service", "montreal.*group*.id2", 0, "echo");

// etc...

In contrast, the following wildcarded realms won't match any instance in the above example:

*
*.*.*.*
montreal.workgroup*.id3

// etc...

# Using the Avid Connector API to Host Services

You can use the Avid Connector API to host C++ services on the Avid Platform.

## Simple Services

The easiest way to host a service on the Avid Platform is to derive an object from `acs::DispatchOperation` and define the `acs::ServiceInfoSP` member.

```c++
const char* ERROR_DIVIDE_BY_ZERO = "DivideByZero";
const char* ERROR_EXCEPTION_CAUGHT = "ExceptionCaught";
const char* ERROR_UNEXPECTED_EXCEPTION_CAUGHT = "UnexpectedExceptionCaught";
const char* ERROR_MISSING_PARAMETER = "MissingParameter";
const char* ERROR_UNABLE_TO_FETCH_PARAMETER_VALUE = "UnableToFetchParameterValue";
const char* ERROR_OUT_OF_MEMORY = "OutOfMemory";

acs::ErrorCode g_ErrorCodes[] = {
{ ERROR_DIVIDE_BY_ZERO, 500, ACS_SEV_ERROR, "Divide by zero.", "" },
{ ERROR_EXCEPTION_CAUGHT, 500, ACS_SEV_ERROR, "Caught exception: %{what}.", "" },
{ ERROR_UNEXPECTED_EXCEPTION_CAUGHT, 500, ACS_SEV_ERROR, "Caught an unexpected exception.", "" },
{ ERROR_MISSING_PARAMETER, 500, ACS_SEV_ERROR, "Missing parameter: '%{parameter}'.", "" },
{ ERROR_UNABLE_TO_FETCH_PARAMETER_VALUE, 500, ACS_SEV_ERROR, "Unable to fetch 'int' value from parameter: '%{parameter}'.", "A parameter is either not present or of the wrong type." },
{ ERROR_OUT_OF_MEMORY, 500, ACS_SEV_ERROR, "Out of memory.", "" }
};

class CalculatorService : public acs::DispatchOperation
{
public:
CalculatorService()
: m_serviceType("example.calculator")
, m_realm("global")
{
createServiceInfo();
}

boost::shared_ptr<acs::ServiceInfo> serviceInfo()
{
return m_serviceInfo;
}

void serviceContext(acs::ServiceContextSP context)
{
m_context = context;
}

private:
bool tryInvoke(const char* operation, acs::Parameters* parameters, acs::ResultsSP results, std::string& errorMessage, acs::ParametersSP errorParameters);
bool internalInvoke(const char* operation, acs::Parameters* parameters, acs::ResultsSP results, std::string& errorMessage, acs::ParametersSP errorParameters);
void createServiceInfo();

boost::shared_ptr<acs::ServiceInfo> m_serviceInfo;
acs::ServiceContextSP m_context;
std::string m_serviceType;
std::string m_realm;
};

void CalculatorService::invoke(const char* operation, acs::Parameters* parameters, acs::OperationContext* responder)
{
responder->ack();

acs::ResultsSP results = acs::ResultsSP(ACSCreateResults());
std::string errorCode;
acs::ParametersSP errorParameters = acs::ParametersSP(ACSCreateParameters(), ACSDestroyParameters);
const bool success = tryInvoke(operation, parameters, results, errorCode, errorParameters);
if (success) {
std::cout << "THE RESPONDER MESSAGE" << responder->requestMessage()->getServiceType() << std::endl;
responder->respond(results.get());
}
else
responder->error(errorCode.c_str(), "", errorParameters.get());
}

bool CalculatorService::tryInvoke(const char* operation, acs::Parameters* parameters, acs::ResultsSP results, std::string& errorMessage, acs::ParametersSP errorParameters)
{
try {
if (internalInvoke(operation, parameters, results, errorMessage, errorParameters))
return true;
} catch (const std::bad_alloc&) {
errorMessage = ERROR_OUT_OF_MEMORY;
m_context->busAccess()->serviceStatus(m_context.get(), ACS_STATUS_WARNING, acs::Error::CRITICAL, "Out of memory", "Memory allocation failure");
} catch (const std::exception& ex) {
errorMessage = ERROR_EXCEPTION_CAUGHT;
errorParameters->addParameter("what", acs::Variant(ex.what()));
m_context->busAccess()->serviceStatus(m_context.get(), ACS_STATUS_WARNING, acs::Error::WARNING, "Exception caught", ex.what());
} catch (...) {
errorMessage = ERROR_UNEXPECTED_EXCEPTION_CAUGHT;
m_context->busAccess()->serviceStatus(m_context.get(), ACS_STATUS_WARNING, acs::Error::WARNING, "Exception caught", "Caught an unexpected exception!");
}
return false;
}

bool CalculatorService::internalInvoke(const char* operation, acs::Parameters* parameters, acs::ResultsSP results, std::string& errorCode, acs::ParametersSP errorParameters)
{
int a = 0, b = 0;
/*
** Must be able to handle parameters passed as integers *or* strings (ala an http GET).
*/
if (!boost::iequals(operation, "gcd") && (!acs::try_value(parameters, "num1", a) && !acs::try_value_from_string(parameters, "num1", a))) {
errorCode = ERROR_UNABLE_TO_FETCH_PARAMETER_VALUE;
errorParameters->addParameter("parameter", acs::Variant("num1"));
return false;
}

if (!boost::iequals(operation, "gcd") && (!acs::try_value(parameters, "num2", b) && !acs::try_value_from_string(parameters, "num2", b))) {
errorCode = ERROR_UNABLE_TO_FETCH_PARAMETER_VALUE;
errorParameters->addParameter("parameter", acs::Variant("num2"));
return false;
}

/*
** Execute methods and assign results...
*/
if (boost::iequals(operation, "add"))
results->addResult("sum", acs::Variant(a+b));
else if (boost::iequals(operation, "subtract"))
results->addResult("difference", acs::Variant(a-b));
else if (boost::iequals(operation, "multiply"))
results->addResult("product", acs::Variant(a*b));
else if (boost::iequals(operation, "divide")) {
if (b == 0) {
errorCode = ERROR_DIVIDE_BY_ZERO;
return false;
}
results->addResult("quotient", acs::Variant(a/b));
}

return true;
}

void CalculatorService::createServiceInfo()
{
m_serviceInfo = boost::shared_ptr<acs::ServiceInfo>(
ACSCreateServiceInfo3(m_serviceType.c_str(), m_realm.c_str(), 5,
"A calculator implemented in C++!",
sizeof(g_ErrorCodes)/sizeof(g_ErrorCodes[0]), g_ErrorCodes), ACSDestroyServiceInfo);

m_serviceInfo->addOperation("add", "Adds two numbers", "add", (std::string("{") +
"\"serviceType\":\"" + m_serviceType + "\","
"\"serviceRealm\": \"" + m_realm + "\","
"\"serviceVersion\": 5,"
"\"op\": \"add\","
"\"paramSet\": {"
"\"num1\": 5,"
"\"num2\": 3"
"}}").c_str(),
"calculator/add/{num1}/{num2}", ACS_HTTP_GET, 0, "sum");
m_serviceInfo->addOperation("subtract", "Subtracts two numbers", "subtract", (std::string("{") +
"\"serviceType\":\"" + m_serviceType + "\","
"\"serviceRealm\": \"" + m_realm + "\","
"\"serviceVersion\": 5,"
"\"op\": \"subtract\","
"\"paramSet\": {"
"\"num1\": 5,"
"\"num2\": 3"
"}}").c_str(),
"calculator/subtract/{num1}/{num2}", ACS_HTTP_GET, 0, "difference");
m_serviceInfo->addOperation("multiply", "Multiplies two numbers", "multiply", (std::string("{") +
"\"serviceType\":\"" + m_serviceType + "\","
"\"serviceRealm\": \"" + m_realm + "\","
"\"serviceVersion\": 5,"
"\"op\": \"multiply\","
"\"paramSet\": {"
"\"num1\": 3,"
"\"num2\": 5"
"}}").c_str(),
"calculator/multiply/{num1}/{num2}", ACS_HTTP_GET, 0, "product");
m_serviceInfo->addOperation("divide", "Divides two numbers", "divide", (std::string("{") +
"\"serviceType\":\"" + m_serviceType + "\","
"\"serviceRealm\": \"" + m_realm + "\","
"\"serviceVersion\": 5,"
"\"op\": \"divide\","
"\"paramSet\": {"
"\"num1\": 6,"
"\"num2\": 2"
"}}").c_str(),
"calculator/divide/{num1}/{num2}", ACS_HTTP_GET, 0, "quotient");
}

The following code snippet illustrates how to create a connection to the Avid Platform, and register an instance of the service on it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <iostream>
#include "CalculatorService.h"

class ACSBUS_PUBLIC_CLASS AsyncRegisterServiceResultImpl : public acs::AsyncRegisterServiceResult
{
public:
virtual void ACSBUS_CALL onSuccess(ServiceContext* context)
{
// do something specific
}

virtual void ACSBUS_CALL onError(const OperationError* error)
{
// do soimething wtih error
}
};

int main(int , char**)
{
boost::shared_ptr<acs::ConnectionInfo> connectionInfo(ACSCreateConnectionInfo0(), ACSDestroyConnectionInfo);
boost::shared_ptr<acs::BusAccess> bus(ACSCreateBusAccess7(connectionInfo.get()), ACSDestroyBusAccess);
if (result) {
boost::shared_ptr<const char> msg(ACSErrorMessage(result), ACSFreeStr);
std::cerr << "Failed to connect to the platform: " << msg;
return -1;
}

CalculatorService theCalc;
boost::shared_ptr<acs::AsyncRegisterServiceResult> arsr = boost::shared_ptr<acs::AsyncRegisterServiceResult>(new AsyncRegisterServiceResultImpl());


acs::ServiceContext* s = 0;
result = bus->registerService( theCalc.serviceInfo().get(), &s, arsr.get() );
if (result) {
boost::shared_ptr<const char> msg(ACSErrorMessage(result), ACSFreeStr);
std::cerr << "Failed to register the service: " << msg;
return -1;
}
acs::ServiceContextSP serviceContextPtr(s, ACSDestroyServiceContext);

bus->runService(serviceContextPtr.get(), static_cast<acs::DispatchOperation*>(&theCalc));

return 0;
}

Structured Errors

A service must declare its complete list of possible error codes when creating its acs::ServiceInfo, via an array of acs::ErrorCosde (see ACSCreateServiceInfo3 and ACSCreateServiceInfo4).

The error code is part of the service’s public API and therefore should be a short sentence in uppercase wtih an underscore used as a separator, e.g. BAD_REQUEST, BULK_DUPLICATION, MISSING_ARGUMENT, QUOTA_EXCEEDED.
The status parameter is the appropriate corresponding HTTP error code.
The error messageTemplate is text in en_US locale, that may include %{identifier} placeholders for error message parameters. The messageTemplate (and any parameters) should convery meaningful information e.g. “Quota on %{resourceName} exceeded for %{projectName}.”
The description parameter may contain an elaborate description of an issue for internal use only that will only appear in error logs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

const char* ERROR_DIVIDE_BY_ZERO = "DivideByZero";
const char* ERROR_EXCEPTION_CAUGHT = "ExceptionCaught";
const char* ERROR_UNEXPECTED_EXCEPTION_CAUGHT = "UnexpectedExceptionCaught";
const char* ERROR_MISSING_PARAMETER = "MissingParameter";
const char* ERROR_UNABLE_TO_FETCH_PARAMETER_VALUE = "UnableToFetchParameterValue";
const char* ERROR_OUT_OF_MEMORY = "OutOfMemory";

acs::ErrorCode g_ErrorCodes[] = {
{ ERROR_DIVIDE_BY_ZERO, 500, ACS_SEV_ERROR, "Divide by zero." },
{ ERROR_EXCEPTION_CAUGHT, 500, ACS_SEV_ERROR, "Caught exception: %{what}."},
{ ERROR_UNEXPECTED_EXCEPTION_CAUGHT, 500, ACS_SEV_ERROR, "Caught an unexpected exception." },
{ ERROR_MISSING_PARAMETER, 500, ACS_SEV_ERROR, "Missing parameter" '%{parameter}'." },
{ ERROR_UNABLE_TO_FETCH_PARAMETER_VALUE, 500, ACS_SEV_ERROR, "Unable to fetch 'int' value from parameter: '%{parameter}'." },
{ ERROR_OUT_OF_MEMORY, 500, ACS_SEV_ERROR, "Out of memory." }
};

// ...

void CalculatorService::createServiceInfo()
{
m_serviceInfo = boost::shared_ptr<acs::ServiceInfo>(
ACSCreateServiceInfo3(m_serviceType.c_str(), m_realm.c_str(), CPP_CALCULATOR_SERVICE_VERSION,
"A calculator implemented in C++!",
sizeof(g_ErrorCodes)/sizeof(g_ErrorCodes[0]), g_ErrorCodes), ACSDestroyServiceInfo);

// ...
}

A service responds with error(s) by passing the error code (along with any required parameters) to the appropriate acs::OperationContext method.

1
2
3
4
5
6
7

// ...
ACSResult error(const char* code, const char* details);
ACSResult error(const char* code, const char* details, const char* paramName, ACSVariant paramValue);
ACSResult error(const char* code, const char* details, const char* paramName, const char* paramValue);
ACSResult error(const char* code, const char* details, acs::Parameters* parameters);

1
2
3
4
5
6
7
8
9
10
11
12

void CalculatorService::invoke(const char* operation, acs::Parameters* parameters, acs::OperationContext* responder)
{
// ...
acs::ParametersSP errorParameters = acs::ParametersSP(ACSCreateParameters(), ACSDestroyParameters);
// ...
errorParameters->addParameters("parameter", acs::Variant("num1"));

responder->error(ERROR_UNABLE_TO_FETCH_PARAMETER_VALUE, "acs::try_value and acs::try_value_from_string for 'num1' failed", errorParameters.get());

}

Multi-Zone Services

The default behavior of the Avid Connector API is to register services in the local zone scope. This means that by default services only receive requests from clients within the same zone. If the local zone is initialized in a multi-zone environment, however, it is possible to register a service in the multi-zone scope. This allows the service to be invoked by clients in any connected zone.

To register a service in the multi-zone scope, use the bus->multiZone() object:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Note: Passing a service type to a service is *very* unusual, but handy in order to test some scenarios.
boost::shared_ptr<CalculatorService> theService(new CalculatorService());

// How the service connects to the bus is driven by environment variables: ACS_GATEWAY_PORT and ACS_GATEWAY_HOST.
boost::shared_ptr<acs::ConnectionInfo> connectionInfo(ACSCreateConnectionInfo0(), ACSDestroyConnectionInfo);
boost::shared_ptr<acs::BusAccess> bus(ACSCreateBusAccess7(connectionInfo.get()), ACSDestroyBusAccess);
ACSResult result = bus->connect();
if (result) {
boost::shared_ptr<const char> msg(ACSErrorMessage(result), ACSFreeStr);
std::cerr << "Failed to connect to the platform (" << msg << ")";
return -1;
}
// Register this service in multi-zone scope. This allows the service to be invoked by clients in any connected zone.
boost::shared_ptr<acs::BusAccessHost> multiZoneBus(bus->multiZone(), ACSDestroyBusAccessHost);

acs::ServiceContext* s = 0;
acs::ServiceEventHandlers handlers = {0};
handlers.serviceEventHandlersSize = sizeof(acs::ServiceEventHandlers);
handlers.registeringEventHandler = boost::dynamic_pointer_cast<acs::RegisteringEventHandler>(theService).get();
handlers.serviceConfigurationEventHandler = boost::dynamic_pointer_cast<acs::ServiceConfigurationEventHandler>(theService).get();
result = multiZoneBus->registerService(theService->serviceInfo().get(), &handlers, &s);
if (result) {
std::cerr << "Failed to register service: " << ACSErrorMessage(result);
return -1;
}
boost::shared_ptr<acs::ServiceContext> serviceContextPtr(s, ACSDestroyServiceContext);
theService->serviceContext(serviceContextPtr);

Local Zone Scope

Registering a service using the bus->localZone() object is functionally equivalent to registering it using the base bus object. The service is accessible within the local zone.

Avid Platform Service Events

Your service can indicate an interest in onRegistering and onConfigureService notifications, by re-implementing the corresponding methods in RegisteringEventHandler and ServiceConfigurationEventHandler. In addition, there are ChannelEventHandler and ConnectionEventHandler for channel and connection-related “events”.

Service Configuration Changes

When a service’s configuration changes, the onConfigureService method is called. The current service context and configuration are passed as an acs::ServiceContext* and acs::DataSet respectively.

Providing Service Status

When a service is registered, it starts sending heartbeats to report its current status (in the background). The reported status contains a status code.

By default, as long as the process is still running, the Avid Connector API reports an “OK” status. While this may be acceptable for simple services (like our calculator example), more complex services can explicitly supply their own status information. The C++ Connector API supports 5 status statues (OK, WARNING, ERROR, SUSPENDED, OFFLINE). Each state has its own behavior which define whether the service is still visible on the platform, and if it is able to recieve messages. See the table below to see the behavior defined by each status.

Status Visible on Platform Receives Requests Example Use Case
OK Yes Yes Service is fully functional (default state)
WARNING Yes Yes Service is still fully functioning, but want to warn about some resource (ie. high memory useage, large db latency, many timeouts to another service)
ERROR Yes No Service is not functioning properly, needs some intervention to fix (ie. ran out of memory, but wish to keep the process alive for debugging)
SUSPENDED Yes No Service is ok, but cannot function properly due to an external resource (ie. DB connection lost and service cannot proceed without persisting data)
OFFLINE No No Service process should stay alive, but should not be visible or routed to (ie. Keep service process alive during DB migration)

To set service status, use the ACSStatusCode data type, and call acs::BusAccessHost::serviceStatus(acs::ServiceContext* serviceContext, ACSStatusCode state, acs::AsyncOpResult* aor) or acs::BusAccessHost::serviceStatus(acs::ServiceContext* serviceContext, ACSStatusCode state, const char* code, const char* details, acs::AsyncOpResult* aor).

Providing Custom Service Health Information

One of the core operations provided by the Avid Connector API is serviceHealth. By default if the service receives this operation request it replies back with the following resultSet:

1
2
3
4
5
6
7
{
"service": {
"instanceId": "955742c3-1fdd-4edd-be71-78aeb837aaa0",
"healthStatus": "ok",
"healthVerifier": "default"
}
}

Service developers may override the serviceHealth operation by providing specific healthStatus or additional customHealthInfo. In this case, serviceHealth response may look like:

1
2
3
4
5
6
7
8
9
10
11
{
"service": {
"customHealthInfo": {
"str": "some string data",
"flag": true
},
"instanceId": "955742c3-1fdd-4edd-be71-78aeb837aaa0",
"healthStatus": "error",
"healthVerifier": "custom"
}
}

To override the default implementation of the serviceHealth operation (which always returns ACS_HS_OK), indicate so by calling the acs::ServiceInfo::overrideOperation method when creating the service’s ServiceInfo:

1
2
3
4
5
6
7
8
9
10
11
void CalculatorService::createServiceInfo()
{
m_serviceInfo = boost::shared_ptr<acs::ServiceInfo>(
ACSCreateServiceInfo3(m_serviceType.c_str(), m_realm.c_str(), 5,
"A calculator implemented in C++!",
sizeof(g_ErrorCodes)/sizeof(g_ErrorCodes[0]), g_ErrorCodes), ACSDestroyServiceInfo);

// ...

m_serviceInfo->overrideOperation(acs::ServiceHealthOperation);
}

Then implement the serviceHealth operation in your invoke method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void CalculatorService::invoke(const char* operation, acs::Parameters* parameters, acs::OperationContext* responder)
{
acs::ResultsSP results = acs::ResultsSP(ACSCreateResults());

// ...

if (boost::iequals(operation, acs::ServiceHealthOperation)) {
acs::HealthStatusSP status(ACSCreateHealthStatus(), ACSDestroyHealthStatus);
// Reply with custom health data (valid JSON text).
status->setStatus(ACS_HS_OK, "{ \"str\": \"some string data\", \"flag\" : true }"); // silly example custom 'health' data
results->addResult(responder->requestMessage(), status.get());
return;
}

// ...
responder->respond(results.get());
}

Providing Compatible Version

Services on the Avid platform provide the concept of ‘Compatible Versions’ to provide a way to remain backwards compatibility with older versions of your service. When you declare that a newer version of your service is compatible with previous versions, it gains the ability to receive messages which were sent to an older version. This way clients who are sending messages to an older version of your service, will get a valid response, even if there are no instances of the old service running. For example, if we declare a new version of our service to be version == 3, but we also declare that it is compatible with versions 2 & 1, then any message which is sent to version 1, 2, or 3 of your service will be routed to version 3 if there is no running instance of your service in the specified version. To declare your service as compatible with other versions we add a compatibleVersions array and size to ACSCreateServiceInfo4.

1
2
3
4
5
6
7
8
9
10
11
12
13

const int compatibleVersions[] = {2, 1};
m_serviceInfo = boost::shared_ptr<acs::ServiceInfo>(
ACSCreateServiceInfo3(m_serviceType.c_str(),
m_realm.c_str(),
3,
sizeof(compatibleVersions)/sizeof(compatibleVersions[0]),
compatibleVersions,
"A calculator implemented in C++!",
sizeof(g_ErrorCodes)/sizeof(g_ErrorCodes[0]),
g_ErrorCodes),
ACSDestroyServiceInfo);

Using the Avid Connector API for Channels

Channels are analogous to Java Message Service (JMS) API Topics. When a message is posted to a channel, it is broadcast as a one-way communication to all the subscribers listening to that channel. It is important to note that channel messages are not persisted, and if you post to a channel that has no subscribers, it will not result in an error.

Channels are identified by their name. Subscribers interested in listening on a channel must either receive the channel name from the service that owns the channel, or use a predefined or well-known channel name.

Note: The names of channels should be namespaced and end with a past-tense verb. The reason for using past tense is, generally speaking, channel messages convey “facts” - i.e. notifications of something that has already happened. For example, avid.protools.project.status.changed.

Posting to a Channel

Channel messages are very similar to regular Avid Platform messages, but rather than have Parameters or Results, they simply have a Data member. The BusAccess API provides a method for posting to a channel:

1
2
3
4
5
6
7
boost::shared_ptr<acs::ChannelMessage> durationMsg(ACSCreateChannelMessage((m_serviceType + ".performance").c_str(), operation), ACSDestroyChannelMessage);
const double e = elapsed(now, then);
durationMsg->dataSet()->add("ticks", ACSCreateVariant(e));
durationMsg->dataSet()->add("operation", ACSCreateVariant(operation));
durationMsg->dataSet()->add("tally", ACSCreateVariant(m_opCounters[operation]));

m_context->busAccess()->postToChannel(durationMsg.get(), opResult);

NOTE: Once a channel is registered, anyone knowing the channel name can post to it. Although the poster is often the creator of the channel, this is not enforced in any way. This treatment is subject to change in a future edition of the API.

Subscribing to a Channel

Services that need to subscribe to channels can do so by subscribing through the BusAccess interface. First, a channel subscriber must implement the `` interface:

1
2
3
4
5
6
7
8
9
10
11
12
class ACSBUS_PUBLIC_CLASS ChannelMessageSubscriber
{
public:
virtual void ACSBUS_CALL onChannelMessage(const acs::ChannelMessageContext& channelContext) = 0;
};

class ACSBUS_PUBLIC_CLASS acs::ChannelMessageContext
{
public:
virtual acs::ChannelMessage& ACSBUS_CALL getChannelMessage() const = 0;
virtual acs::BusAccess& ACSBUS_CALL getBusAccess() const = 0;
};

Note: In versions of the API less than 3.6.1, the ChannelEventHandler interface was used to subscribe to channel messages (rather than ChannelMessageSubscriber).

A ChannelSubscriber can be subscribed to a channel using subscribeToChannel() in BusAccess. Note that a single subscriber may subscribe to multiple channels at once. There is enough data in each of the subscriber methods for the subscriber to identify the channel to which the message pertains.

1
2
3
4
5
virtual ACSResult ACSBUS_CALL subscribeToChannel( const char* channelName, //!< The channel name.
acs::ChannelBindings* bindings,
acs::ChannelMessageSubscriber* subscriber,
acs::AsyncOpResult* aor
) = 0;

Shared Channels

Clients can also subscribe to a shared channel by shared name, channel name and bindings. In this case, when multiple instances are subscribed to the same channel with the same shared name, only one instance will receive each message.

1
virtual ACSResult ACSBUS_CALL subscribeToSharedChannel(const char* channelName, acs::ChannelMessageSubscriber*) = 0;

Channel Bindings

Bindings can be used to filter which messages are sent to subscribers. When subscribing to a channel, pass along a list of bindings to specify which messages are relevant to the subscriber:

1
2
3
4
5
6
7
8
9
10
11
12
// "Bind" two different channel event handlers...
boost::shared_ptr<acs::ChannelBindings> bindings(ACSCreateChannelBindings(), ACSDestroyChannelBindings);
bindings->addBinding("add");
bus->subscribeToChannel(sServiceTypePerfChan.c_str(), bindings.get(), &addPerfReporter, opResult.get());

boost::shared_ptr<acs::ChannelBindings> bindings2(ACSCreateChannelBindings(), ACSDestroyChannelBindings);
bindings2->addBinding("subtract");
bus->subscribeToChannel(sServiceTypePerfChan.c_str(), bindings2.get(), &subPerfReporter, opResult.get());

boost::shared_ptr<acs::BusMessage> ready(ACSCreateBusMessage1(sServiceType.c_str(), "global", "serviceStatus"));

ACSResult r = bus->zone(sTargetZone.c_str())->send(ready.get(), opResult.get());

The binding string will filter messages based on their dot-separated subject. The ‘*‘ character can be used as a wildcard to match entire words. For example, “com.*.info” will match “com.MapService.info” but “com.Map*.info” will not.

Unsubscribing from a Channel

To unsubscribe your subscriber from a specific channel:

1
busAccessHost->unsubscribeFromChannel(channelName.c_str(), static_cast<acs::ChannelMessageSubscriber*>(&cms), opResult.get());

To unsubscribe your subscriber from all channels:

1
multiZoneBus->unsubscribeFromChannels(static_cast<acs::ChannelMessageSubscriber*>(&cms), opResult.get());

Remote Zone and Multi-Zone Channel Communications

The default behavior of the Avid Connector API is to scope channel communications within the local zone. All the examples given above use this default behavior. If the local zone has been initialized in a multi-zone environment, however, it is possible to communicate using channels across multiple zones.

Interacting with Channel Subscribers in a Specific Remote Zone

To communicate with channel subscribers in a specific remote zone, the bus.Zone(String zoneID) object should be used. The following examples send channel events and messages to subscribers in a specific zone only:

1
2
3
4
5
6
7
8
9
10
if (zone == zones.find("local")->second) {
busClient.reset(bus->localZone(), ACSDestroyBusAccessClient);
} else if (zone.c_str() == zones.find("multizone")->second) {
busClient.reset(bus->multiZone(), ACSDestroyBusAccessClient);
} else {
std::cout << "Cannot query specific zone" << std::endl;
}

acs::ChannelMessageSP message(ACSCreateChannelMessage(channelName.c_str(), msg.c_str()), ACSDestroyChannelMessage);
ACSResult result = busClient->postToChannel(message.get(), opResult.get());

In the above case, only multi-zone subscribers to the channel in the remote zone with ID zoneId receive these channel events and messages. If there are local scope subscribers listening on the same channel in that zone, they do not receive the messages (since they only receive messages sourced from their local zone).

Interacting with Channel Subscribers in All Zones

To communicate with channel subscribers in all connected zones, use the return from bus.multiZone method.

1
bus->multiZone()->postToChannel( acs::ChannelMessage* msg, AsyncOpResult* aor );

In the above case, all multi-zone subscribers to the channel across all connected zones will receive these channel events and messages. The local scope subscribers in the poster’s zone will also receive these messages. The local scope subscribers in remote zones, however, will not receive these messages.

Subscribing to Multi-Zone Channels

If the local zone has been initialized in a multi-zone environment, channel subscribers can listen using multi-zone scope. This means that they can receive channel messages from posters in remote zones.

To subscribe to a channel in the multi-zone scope, use the bus->multiZone() method:

1
bus->multiZone()->subscribeToChannel(channelName, subscriber, opResult.get());

In the above example, all multi-zone subscribers to the channel across all connected zones receive the channel events and messages. The local scope subscribers in the poster’s zone also receive the messages. The local scope subscribers in remote zones, however, do not receive the messages.

Local Zone Communications

Note that there are also channel methods in the bus->localZone() object. Invoking channel methods on this object is functionally equivalent to invoking the same methods on the base bus object.

Exposing Operations as REST requests

To declare that an operation supports a REST request, declare the specifics via acs::ServiceInfo::addOperation():

1
2
3
4
5
6
7
8
9
10
11
12
13
class ACSBUS_PUBLIC_CLASS acs::ServiceInfo
{
public:
// ...
virtual ACSResult ACSBUS_CALL addOperation(const char* name, const char* description, const char* exampleName, const char* exampleContent,
const char* restPath =0, // Template of the service URI, curly brackets must be used to declare template parameter in the path, i.e. my/service/{resourceId}/{subresourceId}.
ACSHttpMethod restMethod =ACS_HTTP_GET, // HTTP method for given operation.
const char* restBodyParam =0, // Name of the body parameter.
const char* resultParam =0, // Name of the result parameter.
size_t nRestQueryParams =0, // Number of strings in restQueryParams.
const char** restQueryParams =0) =0;// Array of query parameter names.
// ...
};

For more detailed information about how REST requests are mapped and delivered to your service,
please view the upstream HTTP docs.

Debugging

Debug Symbols

Debug symbols for the Connector API DLL (libavid-cppacs.dll) can be obtained by installing the NuGet package “Avid Connector API for C++ Symbols” (avid-acs-proxybal-cpp.symbols) for Visual Studio 2015 or “Avid Connector API for C++ (vc120) Symbols” (avid-acs-proxybal-cpp-vc120.symbols) for Visual Studio 2013, then copying the libavid-cppacs.pdb that is deep in the packages sub-folder e.g. packages\avid-acs-proxybal-cpp-vc120.symbols.3.6.1-build-0024-g823640a\build\native\bin\x64\v120\Debug to your bin/output folder.

Migrating to the Asynchronous Methods of BusAccess

In version 3.5 of the C++ Connector API, asynchronous versions of the query, send, broadcast, and postToChannel methods were added to the BusAccess interface.

Overloaded versions of the query method were added that accept an additional AsyncQueryResult parameter. A method of AsyncQueryResult will eventually get called whenever the query method completes:

1
2
3
4
5
6
7
class ACSBUS_PUBLIC_CLASS acs::AsyncQueryResult : public acs::ReferenceCount
{
public:
virtual void ACSBUS_CALL onSuccess(BusMessage* message) = 0;
virtual void ACSBUS_CALL onError(const OperationError* error) = 0;
virtual void ACSBUS_CALL onTimeout() = 0;
};

Overloaded versions of the send, broadcast, and postToChannel methods each accept an additional AsyncOpResult parameter.

Migrating to these asynchronous methods is easly done by implementing the AsyncOpResult or AsyncQueryResult classes.

1
2
3
4
5
6
class ACSBUS_PUBLIC_CLASS acs::AsyncOpResult : public acs::ReferenceCount
{
public:
virtual void ACSBUS_CALL onSuccess() = 0;
virtual void ACSBUS_CALL onError(const OperationError* error) = 0;
};

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

class CalculatorResult
: public acs::ReferenceCounter<CalculatorResult>
, public acs::AsyncQueryResult
{
public:
typedef boost::intrusive_ptr<CalculatorResult> pointer;

CalculatorResult() : m_value(0) { }

virtual void ACSBUS_CALL onSuccess(acs::BusMessage* response) {
const ACSVariant *result = response->getResults()->at(0)->value();
m_value = ACSVariant_cast<int>(result);
std::cout << "The calculator responded with the result " << m_value << std::endl;
}

virtual void ACSBUS_CALL onError(const acs::OperationError* error) {
std::cout << "The calculator responded with an error: " << error << std::endl;
}

virtual void ACSBUS_CALL onTimeout() {
std::cout << "The query timed out." << std::endl;
}

virtual void ACSBUS_CALL addRef() const
{
intrusive_ptr_add_ref(this);
}

virtual void ACSBUS_CALL release() const
{
intrusive_ptr_release(this);
}

int getValue() { return m_value; }

private:
int m_value;
};

// ...

acs::BusMessageSP request(ACSCreateBusMessage2("avid.acs.calculator", "global.test", 3, "add"));
request->getParameters()->addParameter("num1", acs::Variant(2));
request->getParameters()->addParameter("num2", acs::Variant(2));

CalculatorResult::pointer addResult(new CalculatorResult());
bus->query(request.get(), addResult.get());

And:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class OperationResult
: public acs::ReferenceCounter<OperationResult>
, public acs::AsyncOpResult
{
public:
typedef boost::intrusive_ptr<OperationResult> pointer;

OperationResult(const std::string& op) : m_op(op) {}

virtual void ACSBUS_CALL onSuccess()
{
std::cout << m_op << " succeeded." << std::endl;
}

virtual void ACSBUS_CALL onError(const acs::OperationError* error)
{
std::cerr << "Received error: " << error->message() << std::endl;
std::cerr << "Received error type: " << error->type() << std::endl;
}

virtual void ACSBUS_CALL addRef() const
{
intrusive_ptr_add_ref(this);
}

virtual void ACSBUS_CALL release() const
{
intrusive_ptr_release(this);
}

private:
const std::string m_op;
};

// ...
OperationResult::pointer subscribeSubmitResult(new OperationResult("Subscribe to channel (\"cpp.service.jobs/submit\")"));
bus->subscribeToChannel("cpp.service.jobs", submittedBinding.get(), &jobStartedEventHandler, subscribeSubmitResult.get());

Migrating to OperationContext, ChannelMessageContext and ChannelMessageSubscriber interfaces

In version 4.0 of the C++ Connector API, the acs::Responder interface was replaced by the acs::OperationContext interface (which adds the concept of a “context” when processing a service operation). The context is propagated to other services through the BusAccess instance returned by the acs::OperationContext.getBusAccess() method. This instance must be used when making any subsequent calls to the bus during the processing of a service operation.

Migrating code from the acs::Responder interface to acs::OperationContext involves modifying your service to support the acs::DispatchOperation interface and replacing all calls to any private or global instance of acs::BusAccess with a call to the acs::OperationContext::getBusAccess() method. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class CalculatorService : public acs::DispatchOperation
, public acs::ChannelMessageSubscriber
{
public:
// ...
virtual void ACSBUS_CALL invoke(const char* operation, acs::Parameters* parameters, const acs::OperationContext& operationContext)
{
// ...
}

virtual void ACSBUS_CALL onChannelMessage(const acs::ChannelMessageContext& channelContext)
{
std::ostringstream oss;
oss << "Received channel message: " << channelContext.getChannelMessage() << std::endl;
}
};

The acs::ChannelSubscriber interface was also replaced with a “context aware” acs::ChannelMessageSubscriber. The acs::ChannelMessageSubscriber::onChannelMessage() method is passed a reference to an acs::ChannelMessageContext, which has methods to access the incoming ChannelMessage and the context aware instance of acs::BusAccess. This instance must be used when making any subsequent calls to the bus during the processing of the channel message.

1
2
3
4
5
6
class ACSBUS_PUBLIC_CLASS acs::ChannelMessageContext
{
public:
virtual acs::ChannelMessage& ACSBUS_CALL getChannelMessage() const = 0; // Access the channel message.
virtual acs::BusAccess& ACSBUS_CALL getBusAccess() const = 0; // Instance of BusAccess appropriate for the operation's context.
};