- Prerequisites
- Creating the Project Structure
- Connecting to the Platform
- Creating a Service
- Sending Requests to Another Service
- Posting Events when the Job is Submitted And Completed
- Subscribing to Service Events
- Sending REST Requests to Your Service
Prerequisites
Before proceeding with the tutorial, please install the following software:
- Microsoft Visual Studio 2013 or 2015 (any version i.e. Community, Professional, or Enterprise)
- Oracle VM VirtualBox (V5.0.8r103449 or later) and Oracle VM VirtualBox Extension Pack (matching version).
- HashiCorp Vagrant (V1.8.1 or later).
- Avid Platform Virtual Machine (avid-platform-vagrant-vm-YYYY-MM-DD-XXXX.zip)
- Avid Connector API for C++ (avid-connector-api-cpp-TTT-XXXX.zip)
Note: All of the code and files for this tutorial, including Visual Studio solution files, can be found in the examples\cpp-service
folder (part of the avid-connector-api-cpp-TTT-XXXX.zip file).
ExampleService.sln
is a Visual Studio 2015 solution file and ExampleService-vc12.sln
is a Visual Studio 2013 file.
The Windows C++ Connector API is available as a NuGet package and is included in the examples\cpp-service\win\ExampleService\libs
folder.
After opening the already completed example source code solution file, missing NuGet packages and the Connector API NuGet package need to be installed.
To install the dependencies and C++ Connector API NuGet package in Visual Studio 2013:
- Right-click on the top-most Solution node in the Solution Explorer. Select Manage NuGet Packages for Solution…
- First, press/choose the “Restore” button to install all of the the dependencies.
- Press/choose the “Online” item on the left-hand side and then press/choose “BuildFeed”.
- Select/choose the “Include Prelease” in the middle drop-down list.
- Find the “
Avid Connector API for C++ (vc120)
in the list with an Id ofavid-acs-proxybal-cpp-vc120
. Press the Install button. In the subsequent window, install the package into all of the projects. Wait for a green check mark to appear. - Close/exit the Manage NuGet Pagkages window.
To install the dependencies and C++ Connector API NuGet package in Visual Studio 2015:
- Right-click on the top-most Solution node in the Solution Explorer. Select Manage NuGet Packages for Solution…
- First, press/choose the “Restore” button to install all of the the dependencies.
- With “Package source” drop-down set to BuildFeed and the “Include prerelease” drop-down/check-box selected, select Browse. Select
avid-acs-proxybal-cpp by Avid Technology, Inc.
in the list of packages. - On the right-hand side select all of the projects and press/choose the Install button. Wait for “========== Finished ==========” to be displayed in the Output window.
Once all of the packages are installed, you should be able to build the project files via Build -> Build Solution.
Creating the Project Structure
Create a new Windows Console project by selecting
File->New->Project...
.In the New Project dialog, navigate to
Templates->Other Languages->Visual C++->Win32
and select theWin 32 Console Application
template.Note: If
Win32
does not appear in the Visual C++ category, install the “Visual C++ Tools for Windows Desktop” as prompted.Set the project name to
Tutorial
and clickOK
to begin the project creation process.In the Win 32 Application Wizard that appears, click
Next
.On the Application Settings page of the wizard, ensure the following items are selected:
- Console Application
- Precompiled Header
- Security Development Lifecycle (SDL) Checks
Click
Finish
to create the project and close the wizard.In the Solution Explorer pane, rename the
Tutorial.cpp
file tomain.cpp
.In the Solution Explorer pane right click on
Header Files->Add->New Item
and select theHeader File(.h)
template. Name this ExampleService.h.Add the Avid-supplied
libs
folder as a NuGet repository as follows.- Select
Tools->NuGet Package Manager->Package Manager Settings
. - In the Options dialog that appears, choose Package Sources. In the Available Package Sources area of the dialog, click the plus button.
- Name it
Avid C++
, and for theSource
value navigate to thelibs
folder (extracted from the avid-connector-api-cpp-XXXX.zip). - Click OK to perform the operation and close the Options window.
- Select
Add the avid-acs-proxybal-cpp package to solution as follows.
- Select
Project->Manage NuGet Packages...
. - Check the Include prerelease checkbox.
- Select the Browse option and search for avid-acs-proxybal-cpp.
Make sure thePackage source
indicatesAvid C++
. - When the avid-acs-proxybal-cpp package appears in the results list, select the latest version (v3.4.0-build-XXXX at time of writing).
- Click the Install button.
- In the Preview dialog that appears, click OK to accept the changes.
- In the License Acceptance dialog that appears, click the I Accept button.
Note: If it becomes necessary to update a package, it is good practise to eliminate dependency issues by uninstalling all packages associated with a project prior to the update. To uninstall a package associated with a project, right-click the project in the Solution Explorer and select
Manage Nuget Packages for Solution...
from the popup menu.- Select
Close the package manager pane.
Connecting to the Platform
Add the following content to main.cpp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int main(int argc , char* argv[] ) {
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 broker (" << msg << ")";
return -1;
}
std::cout << "Connected to the Bus!" << std::endl;
return 0;
}In order to connect to the Avid Platform Virtual Machine you need to set two environment variables:
1
2ACS_GATEWAY_PORT=9500
ACS_GATEWAY_HOST=avid-platform-zone1Build by selecting
Build->Rebuild Solution
.Run the solution by selecting
Debug->Start Debugging
.You should see the following output:
1
Connected to the Bus!
Creating a Service
Add the following to the ExampleService.h file:
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
class ExampleService : public acs::DispatchOperation {
public:
ExampleService();
~ExampleService();
virtual void ACSBUS_CALL invoke(const char* operation, acs::Parameters* parameters, const acs::OperationContext& operationContext);
bool submitJob(acs::Parameters *parameters, acs::ResultsSP results, std::string &errorCode, acs::ParametersSP errorParameters);
int getParameterAsInt(acs::Parameters *parameters, const char *name);
acs::ServiceInfoSP getServiceInfo();
acs::ServiceInfoSP createServiceInfo();
private:
acs::ServiceInfoSP serviceInfo;
const std::string serviceType;
const std::string serviceRealm;
const int serviceVersion;
public:
static const char* INVALID_JOB_ID;
static const char* INVALID_EXECUTION_TIME;
static const char* UNABLE_TO_UPDATE_NUMBER_OF_SUBMITTED_JOBS;
private:
acs::ErrorCode errorCodes[3];
};
Add the following to the ExampleService.cpp file:
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
const char* ExampleService::INVALID_JOB_ID = "InvalidJobId";
const char* ExampleService::INVALID_EXECUTION_TIME = "InvalidExecutionTime";
const char* ExampleService::UNABLE_TO_UPDATE_NUMBER_OF_SUBMITTED_JOBS = "UpdateFailed";
acs::ErrorCode ExampleService::errorCodes[] = {
{ INVALID_JOB_ID, 500, ACS_SEV_ERROR, "Invalid jobId: %{jobId}" },
{ INVALID_EXECUTION_TIME, 500, ACS_SEV_ERROR, "Invalid execution time: %{execTime}" },
{ UNABLE_TO_UPDATE_NUMBER_OF_SUBMITTED_JOBS, 500, ACS_SEV_ERROR, "Unable to update the number of submitted jobs." }
};
ExampleService::ExampleService() :serviceType(SERVICE_TYPE), serviceRealm(SERVICE_REALM), serviceVersion(SERVICE_VERSION) {
serviceInfo = createServiceInfo();
};
ExampleService::~ExampleService();
void ExampleService::invoke(const char *operation, acs::Parameters *parameters, const acs::OperationContext& operationContext) {
if (boost::iequals(operation, "submitJob"))
submitJob(parameters, operationContext);
};
int ExampleService::getParameterAsInt(acs::Parameters *parameters, const char *name) {
acs::Parameter *param = parameters->find(name);
if (!param) return -1;
const ACSVariant *var = param->value();
return ACSVariant_cast<int>(var);
};
void ExampleService::submitJob(acs::Parameters *parameters, const acs::OperationContext& operationContext)
{
acs::ParametersSP errorParameters = acs::ParametersSP(ACSCreateParameters(), ACSDestroyParameters);
int jobId = getParameterAsInt(parameters, "jobId");
int execTime = getParameterAsInt(parameters, "execTime");
if (jobId < 1) {
errorParameters->addParameter("jobId", acs::Variant(jobId));
operationContext.error(INVALID_JOB_ID, "", errorParameters.get());
} else if (execTime <= 0) {
errorParameters->addParameter("execTime", acs::Variant(execTime));
operationContext.error(INVALID_EXECUTION_TIME, "", errorParameters.get());
} else {
operationContext.respond("status", acs::Variant("submitted"));
}
};
acs::ServiceInfoSP ExampleService::getServiceInfo() {
return serviceInfo;
};
acs::ServiceInfoSP ExampleService::createServiceInfo() {
acs::ServiceInfoSP serviceInfo = boost::shared_ptr<acs::ServiceInfo>(
ACSCreateServiceInfo3(serviceType.c_str(), serviceRealm.c_str(), serviceVersion, "An example c++ service!", sizeof(this->errorCodes) / sizeof(errorCodes[0]), errorCodes),
ACSDestroyServiceInfo);
serviceInfo->addOperation("submitJob",
"Submit job for Execution, respond with info whether job submitted successfully or not",
"submitJob",
(std::string("{") +
"\"serviceType\":\"" + serviceType + "\","
"\"serviceRealm\": \"" + serviceRealm + "\","
"\"serviceVersion\": 1,"
"\"op\": \"submitJob\","
"\"paramSet\": {"
"\"jobId\": 12345,"
"\"execTime\": 5"
"}}").c_str(),
"example/submitJob/{jobId}", ACS_HTTP_POST, 0, "");
return serviceInfo;
};Modify the main.cpp file to create and start the service:
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
class AsyncRegisterServiceResultImpl : public acs::AsyncRegisterServiceResult
{
public:
virtual void onSuccess()
{
// do something successful
}
virtual void onError(const acs::OperationError* error)
{
// do somethign with the error
}
virtual void addRef() const
{
}
virtual void release() const
{
}
};
int main(int argc , char* argv[] ) {
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 broker (" << msg << ")";
return -1;
}
std::cout << "Connected to the Bus!" << std::endl;
acs::ServiceContext *serviceContext = 0;
ExampleService exampleService;
result = bus->registerService(exampleService.getServiceInfo().get(), &serviceContext, arsr.get());
if (result) {
boost::shared_ptr<const char> msg(ACSErrorMessage(result), ACSFreeStr);
std::cerr << "Failed to register the service: " << msg;
return -1;
}
std::cout << "Starting the ExampleService!" << std::endl;
bus->runService(serviceContext, static_cast<acs::DispatchOperation*>(&exampleService));
return 0;
}Rebuild the project and run it. You should see the following output:
1
2Connected to the Bus!
Starting the ExampleService!Test that the service is working:
- Open the ACS Monitor in a web browser and scroll down to locate the
avid.tutorial.cpp.service
. - Expand the Service Operations List for the service. The list should include the
submitJob
operation. - Click the ‘submitJob’ link. Its corresponding message should appear in the code editor area, under the
Request to
field. - Click the ‘Query’ button. Your service’s response appears in the
Response
field.
- Open the ACS Monitor in a web browser and scroll down to locate the
Press
Ctrl+C
in the terminal to terminate the service, or stop it in Visual Studio.
Congratulations! You have successfully registered your service on the Platform.
Sending Requests to Another Service
In this step you will improve the submitJob
method and add a new method, getNumberOfSubmittedJobs
, to update and request the number of submitted jobs. In addition, you will add methods and interfaces to handle asynchronous requests. Modify ExampleService.h:
1 | class SetNumberOfSubmittedJobsResult |
And in ExampleService.cpp:
1 | void ExampleService::submitJob(acs::Parameters *parameters, const acs::OperationContext& operationContext) |
Posting Events when the Job is Submitted and Completed
To post to a channel when a job has started/completed, edit ExampleService.
- Update ExampleService.h:
1 | class ChannelPostResult |
Update ExampleService.cpp:
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// Add these definitions to ExampleService.cpp
// Make sure to include boost/thread/thread.hpp
void ExampleService::postJobStartToChannel(const acs::OperationContext& operationContext, const int jobId, const int execTime) {
acs::ChannelMessageSP startJobMessage(ACSCreateChannelMessage("cpp.service.jobs", "submit"), ACSDestroyChannelMessage);
startJobMessage->dataSet()->add("jobId", acs::Variant(jobId));
ChannelPostResult::pointer postResult(new ChannelPostResult);
operationContext.getBusAccess().postToChannel(startJobMessage.get(), postResult.get());
JobCompletedData data(operationContext, jobId, execTime);
boost::thread t(&ExampleService::postJobCompletedToChannel, this, data);
};
void ExampleService::postJobCompletedToChannel(const int jobId, const int execTime) {
boost::this_thread::sleep(boost::posix_time::seconds(data.execTime));
acs::ChannelMessageSP jobDoneMessage(ACSCreateChannelMessage("cpp.service.jobs", "complete"), ACSDestroyChannelMessage);
jobDoneMessage->dataSet()->add("jobId", acs::Variant(data.jobId));
ChannelPostResult::pointer postResult(new ChannelPostResult);
data.operationContext->getBusAccess().postToChannel(jobDoneMessage.get(), postResult.get());
}
// Update submitJob in ExampleService.cpp
void ExampleService::submitJob(acs::Parameters *parameters, acs::Responder *responder)
{
acs::ParametersSP errorParameters = acs::ParametersSP(ACSCreateParameters(), ACSDestroyParameters);
int jobId = getParameterAsInt(parameters, "jobId");
int execTime = getParameterAsInt(parameters, "execTime");
if (jobId < 1) {
errorParameters->addParameter("jobId", acs::Variant(jobId));
operationContext.error(INVALID_JOB_ID, "", errorParameters.get());
} else if (execTime <= 0) {
errorParameters->addParameter("execTime", acs::Variant(execTime));
operationContext.error(INVALID_EXECUTION_TIME, "", errorParameters.get());
} else {
postJobStartToChannel(operationContext, jobId, execTime);
getNumberOfSubmittedJobs(operationContext);
}
};Rebuild the project to verify it builds without error.
Verify the service in the ACS Monitor.
Congratulations! You have successfully published events to the Platform.
At this point service behavior in the ACS Monitor is unchanged. Next, we create a consumer of our service events.
Subscribing to Service Events
Other services and clients may wish to listen on the channel to which our service posts, so they can receive event information. We demonstrate this functionality by creating a new client that listens on the channel.
Create a new Console Application project by right-clicking on the solution in the Solution Explorer and selecting
Add->New Project...
from the pop-up menu. SelectVisual C++->Windows->Win32->Win32 Console Application
, giving it the nameExampleClient
. Click the OK button to save your changes.Add the following to ExampleClient.cpp
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
/**
* When listening on a channel we must pass in a ChannelEventHandler which will
* delegate what we do with the information received on the channel.
*/
class JobStartedEventHandler : public acs::ChannelMessageSubscriber
{
public:
virtual void onChannelMessage(const acs::ChannelMessageContext& channelContext)
{
const acs::DataSet *data = channelContext.getChannelMessage().dataSet();
int jobId = ACSVariant_cast<int>(data->value("jobId"));
std::cout << "The job " << jobId << " has been submitted" << std::endl;
}
};
class JobCompletedEventHandler : public acs::ChannelMessageSubscriber {
public:
virtual void onChannelMessage(const acs::ChannelMessageContext& channelContext)
{
const acs::DataSet *data = channelContext.getChannelMessage().dataSet();
int jobId = ACSVariant_cast<int>(data->value("jobId"));
std::cout << "The job " << jobId << " has been completed" << std::endl;
}
};
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;
};
int main(int argc, char* argv[])
{
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 broker (" << msg << ")";
return -1;
}
std::cout << "Connected to the Bus!" << std::endl;
JobStartedEventHandler jobStartedEventHandler;
acs::ChannelBindingsSP submittedBinding(ACSCreateChannelBindings(), ACSDestroyChannelBindings);
submittedBinding->addBinding("submit");
OperationResult::pointer subscribeSubmitResult(new OperationResult("Subscribe to channel (\"cpp.service.jobs/submit\")"));
bus->subscribeToChannel("cpp.service.jobs", submittedBinding.get(), &jobStartedEventHandler, subscribeSubmitResult.get());
JobCompletedEventHandler jobCompletedEventHandler;
acs::ChannelBindingsSP completedBinding(ACSCreateChannelBindings(), ACSDestroyChannelBindings);
completedBinding->addBinding("complete");
OperationResult::pointer subscribeCompleteResult(new OperationResult("Subscribe to channel (\"cpp.service.jobs/complete\")"));
bus->subscribeToChannel("cpp.service.jobs", completedBinding.get(), &jobCompletedEventHandler, subscribeCompleteResult.get());
std::cout << "Enter any key to quit client" << std::endl;
char key;
std::cin >> key;
return 0;
}Set up the Tutorial solution to build both the Service and Client applications by right-click on the solution name in the Solution Explorer and selecting
Properties
from the pop-up menu.In the Solution Properties Pages dialog that appears, select
Startup Project
andMultiple startup projects
.In addition, in the Action column, Select Start or Start without debugging for both ExampleService and ExampleClient.
Build the solution using the “Rebuild Solution” option by selecting
Build->Rebuild Solution
.Run the solution by selecting
Debug->Start Debugging
.Go to ACS Monitor
Find cpp.tutorial.service and send submitJob a few times with different values
for jobId.Test the service to verify it is working:
- Open the ACS Monitor, and filter the services list by providing
cpp\.service
regular expression in the box. You should now see now only one service in the list:avid.tutorial.cpp.service
- Send multiple
submitJob
messages to your service with different values for jobID.
- Open the ACS Monitor, and filter the services list by providing
Return to Visual Studio. In the client terminal you should see the following output for each job submitted:
1
2
3The job <jobId> has been submitted
# After <execTime> seconds
The job <jobId> has completed
Congratulations! You have created a client that subscribes to the events being posted by your service.
Sending REST Requests to Your Service
Log in to
avid-platform-zone1
with ssh (username: root; password: vagrant):1
ssh root@avid-platform-zone1
Send POST requests to your service using the
curl
command, use the avidAccessToken generated previously:1
2
3curl -H "Content-Type: application/json" --data '{"execTime":15000}' "http://avid-platform-zone1:8080/apis/avid.tutorial.cpp.service;version=1/jobs/10?_avidAccessToken=ZWEwZDhlOTItYmJhZS00YTQ5LWFiN2QtNTAyYWMwZjhjZjJj"
#NOTE: request should be routed via NGINX and use HTTPS protocol: https://avid-platform-zone1/apis/...Given HTTP request will send following message to your service:
1
2
3
4
5
6
7
8
9
10{
"serviceType": "avid.tutorial.cpp.service",
"serviceRealm": "global",
"serviceVersion": 1,
"op": "submitJob",
"paramSet": {
"jobId": 10,
"execTime": 15000
}
}Press
Ctrl+C
in the shells to terminate both service and client.
Congratulations! You have completed this tutorial.