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.