Example 1 - Service Event ListenerΒΆ
This example creates a simple bundle that listens for service events. This example does not do much at first, because it only prints out the details of registering and unregistering services. In the next example we will create a bundle that implements a service, which will cause this bundle to actually do something. For now, we will just use this example to help us understand the basics of creating a bundle and its activator.
A bundle gains access to the C++ Micro Services API using a unique
instance of cppmicroservices::BundleContext
.
In order for a bundle to get its unique bundle context, it must call
GetBundleContext()
or
implement the cppmicroservices::BundleActivator
interface.
This interface has two methods, Start()
and Stop()
, that both
receive the bundle’s context and are called when the bundle is started
and stopped, respectively.
In the following source code, our bundle implements the BundleActivator
interface and uses the context to add itself as a listener for service
events (in the eventlistener/Activator.cpp
file):
#include "cppmicroservices/BundleActivator.h"
#include "cppmicroservices/BundleContext.h"
#include "cppmicroservices/Constants.h"
#include "cppmicroservices/ServiceEvent.h"
#include <iostream>
using namespace cppmicroservices;
namespace {
/**
* This class implements a simple bundle that utilizes the CppMicroServices's
* event mechanism to listen for service events. Upon receiving a service event,
* it prints out the event's details.
*/
class Activator : public BundleActivator
{
private:
/**
* Implements BundleActivator::Start(). Prints a message and adds a member
* function to the bundle context as a service listener.
*
* @param context the framework context for the bundle.
*/
void Start(BundleContext context)
{
std::cout << "Starting to listen for service events." << std::endl;
listenerToken = context.AddServiceListener(
std::bind(&Activator::ServiceChanged, this, std::placeholders::_1));
}
/**
* Implements BundleActivator::Stop(). Prints a message and removes the
* member function from the bundle context as a service listener.
*
* @param context the framework context for the bundle.
*/
void Stop(BundleContext context)
{
context.RemoveListener(std::move(listenerToken));
std::cout << "Stopped listening for service events." << std::endl;
// Note: It is not required that we remove the listener here,
// since the framework will do it automatically anyway.
}
/**
* Prints the details of any service event from the framework.
*
* @param event the fired service event.
*/
void ServiceChanged(const ServiceEvent& event)
{
std::string objectClass =
ref_any_cast<std::vector<std::string>>(
event.GetServiceReference().GetProperty(Constants::OBJECTCLASS))
.front();
if (event.GetType() == ServiceEvent::SERVICE_REGISTERED) {
std::cout << "Ex1: Service of type " << objectClass << " registered."
<< std::endl;
} else if (event.GetType() == ServiceEvent::SERVICE_UNREGISTERING) {
std::cout << "Ex1: Service of type " << objectClass << " unregistered."
<< std::endl;
} else if (event.GetType() == ServiceEvent::SERVICE_MODIFIED) {
std::cout << "Ex1: Service of type " << objectClass << " modified."
<< std::endl;
}
}
ListenerToken listenerToken;
};
}
CPPMICROSERVICES_EXPORT_BUNDLE_ACTIVATOR(Activator)
After implementing the C++ source code for the bundle activator, we must
export the activator as shown in the last line above. This ensures
that the C++ Micro Services library can create an instance of the activator
and call the Start()
and Stop()
methods.
After implementing the source code for the bundle, we must also define a
manifest file that contains meta-data needed by the C++ Micro Services
framework for manipulating the bundle. The manifest is embedded in the
shared library along with the compiled source code. We create a file
called manifest.json
that contains the following:
{
"bundle.symbolic_name" : "eventlistener",
"bundle.activator" : true
}
Next, we need to compile the source code. This example uses CMake as the build system and the top-level CMakeLists.txt file could look like this:
# [prj-start]
project(CppMicroServicesExamples)
set(CMAKE_CXX_STANDARD_REQUIRED 1)
set(CMAKE_CXX_STANDARD 17)
find_package(CppMicroServices NO_MODULE REQUIRED)
cmake_minimum_required(VERSION ${US_CMAKE_MINIMUM_REQUIRED_VERSION})
cmake_policy(VERSION ${US_CMAKE_MINIMUM_REQUIRED_VERSION})
# [prj-end]
#-----------------------------------------------------------------------------
# Init output directories
#-----------------------------------------------------------------------------
set(CppMicroServicesExamples_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib")
set(CppMicroServicesExamples_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib")
set(CppMicroServicesExamples_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin")
foreach(_type ARCHIVE LIBRARY RUNTIME)
if(NOT CMAKE_${_type}_OUTPUT_DIRECTORY)
set(CMAKE_${_type}_OUTPUT_DIRECTORY ${CppMicroServicesExamples_${_type}_OUTPUT_DIRECTORY})
endif()
endforeach()
function(CreateTutorial _name)
set(_srcs ${ARGN})
usFunctionGetResourceSource(TARGET Tutorial-${_name} OUT _srcs)
usFunctionGenerateBundleInit(TARGET Tutorial-${_name} OUT _srcs)
add_library(Tutorial-${_name} ${_srcs})
set_property(TARGET Tutorial-${_name} APPEND PROPERTY COMPILE_DEFINITIONS US_BUNDLE_NAME=${_name})
set_property(TARGET Tutorial-${_name} PROPERTY DEBUG_POSTFIX "")
if(${_name}_DEPENDS)
foreach(_dep ${${_name}_DEPENDS})
include_directories(${PROJECT_SOURCE_DIR}/tutorial/${_dep})
target_link_libraries(Tutorial-${_name} Tutorial-${_dep})
endforeach()
endif()
target_link_libraries(Tutorial-${_name} ${CppMicroServices_LIBRARIES})
set_target_properties(Tutorial-${_name} PROPERTIES
LABELS Tutorial
OUTPUT_NAME ${_name}
)
usFunctionAddResources(TARGET Tutorial-${_name} BUNDLE_NAME ${_name} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/tutorial/${_name}/resources FILES manifest.json)
usFunctionEmbedResources(TARGET Tutorial-${_name})
endfunction()
add_subdirectory(eventlistener)
and the CMakeLists.txt file in the eventlistener subdirectory is:
set(_srcs Activator.cpp)
CreateTutorial(eventlistener ${_srcs})
The call to usFunctionGenerateBundleInit
creates required callback
functions to be able to manage the bundle within the C++ Micro Services
runtime. If you are not using CMake, you have to place a macro call to
CPPMICROSERVICES_INITIALIZE_BUNDLE
yourself into the bundle’s
source code, e.g. in Activator.cpp
. Have a look at
Build Instructions for more details about using CMake or other
build systems (e.g. Makefiles) when writing bundles.
To run the examples contained in the C++ Micro Services library, we use
a small driver program called usTutorialDriver
:
CppMicroServices-build> bin/usTutorialDriver
> h
h This help text
start <id | name> Start the bundle with id <id> or name <name>
stop <id | name> Stop the bundle with id <id> or name <name>
status Print status information
shutdown Shut down the framework
Typing status
at the command prompt lists all installed bundles and
their current state. Note that the driver program pre-installs the
example bundles, so they will be listed initially with the INSTALLED
state. To start the eventlistener bundle, type start eventlistener
at the command prompt:
> status
Id | Symbolic Name | State
-----------------------------------
0 | system_bundle | ACTIVE
1 | eventlistener | INSTALLED
2 | dictionaryservice | INSTALLED
3 | frenchdictionary | INSTALLED
4 | dictionaryclient | INSTALLED
5 | dictionaryclient2 | INSTALLED
6 | dictionaryclient3 | INSTALLED
7 | spellcheckservice | INSTALLED
8 | spellcheckclient | INSTALLED
> start eventlistener
Starting to listen for service events.
>
The above command started the eventlistener bundle (implicitly loading
its shared library). Keep in mind, that this bundle will not do much at
this point since it only listens for service events and we are not
registering any services. In the next example we will register a service
that will generate an event for this bundle to receive. To exit the
usTutorialDriver
, use the shutdown
command.