Example 6 - Spell Checker Service BundleΒΆ

In this example, we complicate things further by defining a new service that uses an arbitrary number of dictionary services to perform its function. More precisely, we define a spell checker service which will aggregate all dictionary services and provide another service that allows us to spell check passages using our underlying dictionary services to verify the spelling of words. Our bundle will only provide the spell checker service if there are at least two dictionary services available. First, we will start by defining the spell checker service interface in a file called spellcheckservice/ISpellCheckService.h:

#include "cppmicroservices/ServiceInterface.h"

#include <string>
#include <vector>

#ifdef US_BUILD_SHARED_LIBS
#  ifdef Tutorial_spellcheckservice_EXPORTS
#    define SPELLCHECKSERVICE_EXPORT US_ABI_EXPORT
#  else
#    define SPELLCHECKSERVICE_EXPORT US_ABI_IMPORT
#  endif
#else
#  define SPELLCHECKSERVICE_EXPORT US_ABI_EXPORT
#endif

/**
 * A simple service interface that defines a spell check service. A spell check
 * service checks the spelling of all words in a given passage. A passage is any
 * number of words separated by a space character and the following punctuation
 * marks: comma, period, exclamation mark, question mark, semi-colon, and colon.
 */
struct SPELLCHECKSERVICE_EXPORT ISpellCheckService
{
  // Out-of-line virtual desctructor for proper dynamic cast
  // support with older versions of gcc.
  virtual ~ISpellCheckService();

  /**
   * Checks a given passage for spelling errors. A passage is any number of
   * words separated by a space and any of the following punctuation marks:
   * comma (,), period (.), exclamation mark (!), question mark (?),
   * semi-colon (;), and colon(:).
   *
   * @param passage the passage to spell check.
   * @return A list of misspelled words.
   */
  virtual std::vector<std::string> Check(const std::string& passage) = 0;
};

The service interface is quite simple, with only one method that needs to be implemented. Because we provide an empty out-of-line destructor (defined in the file ISpellCheckService.cpp) we must export the service interface by using the bundle specific SPELLCHECKSERVICE_EXPORT macro.

In the following source code, the bundle needs to create a complete list of all dictionary services; this is somewhat tricky and must be done carefully if done manually via service event listners. Our bundle makes use of the cppmicroservices::ServiceTracker and cppmicroservices::ServiceTrackerCustomizer classes to robustly react to service events related to dictionary services. The bundle activator of our bundle now additionally implements the ServiceTrackerCustomizer class to be automatically notified of arriving, departing, or modified dictionary services. In case of a newly added dictionary service, our ServiceTrackerCustomizer::AddingService() implementation checks if a spell checker service was already registered and if not registers a new ISpellCheckService instance if at lead two dictionary services are available. If the number of dictionary services drops below two, our ServiceTrackerCustomizer implementation un-registers the previously registered spell checker service instance. These actions must be performed in a synchronized manner to avoid interference from service events originating from different threads. The implementation of our bundle activator is done in a file called spellcheckservice/Activator.cpp:

#include "IDictionaryService.h"
#include "ISpellCheckService.h"

#include "cppmicroservices/BundleActivator.h"
#include "cppmicroservices/BundleContext.h"
#include "cppmicroservices/ServiceTracker.h"
#include "cppmicroservices/ServiceTrackerCustomizer.h"

#include <cstring>
#include <map>
#include <memory>

US_MSVC_PUSH_DISABLE_WARNING(4996)

using namespace cppmicroservices;

namespace {

/**
 * This class implements a bundle that implements a spell
 * checker service. The spell checker service uses all available
 * dictionary services to check for the existence of words in
 * a given sentence. This bundle uses a ServiceTracker to
 * monitors the dynamic availability of dictionary services,
 * and to aggregate all available dictionary services as they
 * arrive and depart. The spell checker service is only registered
 * if there are dictionary services available, thus the spell
 * checker service will appear and disappear as dictionary
 * services appear and disappear, respectively.
**/
class US_ABI_LOCAL Activator
  : public BundleActivator
  , public ServiceTrackerCustomizer<IDictionaryService>
{

private:
  /**
   * A private inner class that implements a spell check service; see
   * ISpellCheckService for details of the service.
   */
  class SpellCheckImpl : public ISpellCheckService
  {

  private:
    typedef std::map<ServiceReference<IDictionaryService>,
                     std::shared_ptr<IDictionaryService>>
      RefToServiceType;
    RefToServiceType m_refToSvcMap;

  public:
    /**
     * Implements ISpellCheckService::Check(). Checks the given passage for
     * misspelled words.
     *
     * @param passage the passage to spell check.
     * @return A list of misspelled words.
     */
    std::vector<std::string> Check(const std::string& passage)
    {
      std::vector<std::string> errorList;

      // No misspelled words for an empty string.
      if (passage.empty()) {
        return errorList;
      }

      // Tokenize the passage using spaces and punctuation.
      const char* delimiters = " ,.!?;:";
      char* passageCopy = new char[passage.size() + 1];
      std::memcpy(passageCopy, passage.c_str(), passage.size() + 1);
      char* pch = std::strtok(passageCopy, delimiters);

      {
        // Lock the m_refToSvcMap member using your favorite thread library here...
        // MutexLocker lock(&m_refToSvcMapMutex)

        // Loop through each word in the passage.
        while (pch) {
          std::string word(pch);

          bool correct = false;

          // Check each available dictionary for the current word.
          for (RefToServiceType::const_iterator i = m_refToSvcMap.begin();
               (!correct) && (i != m_refToSvcMap.end());
               ++i) {
            std::shared_ptr<IDictionaryService> dictionary = i->second;

            if (dictionary->CheckWord(word)) {
              correct = true;
            }
          }

          // If the word is not correct, then add it
          // to the incorrect word list.
          if (!correct) {
            errorList.push_back(word);
          }

          pch = std::strtok(nullptr, delimiters);
        }
      }

      delete[] passageCopy;

      return errorList;
    }

    std::size_t AddDictionary(const ServiceReference<IDictionaryService>& ref,
                              std::shared_ptr<IDictionaryService> dictionary)
    {
      // Lock the m_refToSvcMap member using your favorite thread library here...
      // MutexLocker lock(&m_refToSvcMapMutex)

      m_refToSvcMap.insert(std::make_pair(ref, dictionary));

      return m_refToSvcMap.size();
    }

    std::size_t RemoveDictionary(
      const ServiceReference<IDictionaryService>& ref)
    {
      // Lock the m_refToSvcMap member using your favorite thread library here...
      // MutexLocker lock(&m_refToSvcMapMutex)

      m_refToSvcMap.erase(ref);

      return m_refToSvcMap.size();
    }
  };

  virtual std::shared_ptr<IDictionaryService> AddingService(
    const ServiceReference<IDictionaryService>& reference)
  {
    std::shared_ptr<IDictionaryService> dictionary =
      m_context.GetService(reference);
    std::size_t count =
      m_spellCheckService->AddDictionary(reference, dictionary);
    if (!m_spellCheckReg && count > 1) {
      m_spellCheckReg =
        m_context.RegisterService<ISpellCheckService>(m_spellCheckService);
    }
    return dictionary;
  }

  virtual void ModifiedService(
    const ServiceReference<IDictionaryService>& /*reference*/,
    const std::shared_ptr<IDictionaryService>& /*service*/)
  {
    // do nothing
  }

  virtual void RemovedService(
    const ServiceReference<IDictionaryService>& reference,
    const std::shared_ptr<IDictionaryService>& /*service*/)
  {
    if (m_spellCheckService->RemoveDictionary(reference) < 2 &&
        m_spellCheckReg) {
      m_spellCheckReg.Unregister();
      m_spellCheckReg = nullptr;
    }
  }

  std::shared_ptr<SpellCheckImpl> m_spellCheckService;
  ServiceRegistration<ISpellCheckService> m_spellCheckReg;

  BundleContext m_context;
  std::unique_ptr<ServiceTracker<IDictionaryService>> m_tracker;

public:
  Activator()
    : m_context()
  {}

  /**
   * Implements BundleActivator::Start(). Registers an
   * instance of a dictionary service using the bundle context;
   * attaches properties to the service that can be queried
   * when performing a service look-up.
   *
   * @param context the context for the bundle.
   */
  void Start(BundleContext context)
  {
    m_context = context;

    m_spellCheckService.reset(new SpellCheckImpl);
    m_tracker.reset(new ServiceTracker<IDictionaryService>(context, this));
    m_tracker->Open();
  }

  /**
   * Implements BundleActivator::Stop(). Does nothing since
   * the C++ Micro Services library will automatically unregister any registered services
   * and release any used services.
   *
   * @param context the context for the bundle.
   */
  void Stop(BundleContext /*context*/)
  {
    // NOTE: The service is automatically unregistered

    m_tracker->Close();
  }
};
}

US_MSVC_POP_WARNING

CPPMICROSERVICES_EXPORT_BUNDLE_ACTIVATOR(Activator)

Note that we do not need to unregister the service in Stop() method, because the C++ Micro Services library will automatically do so for us. The spell checker service that we have implemented is very simple; it simply parses a given passage into words and then loops through all available dictionary services for each word until it determines that the word is correct. Any incorrect words are added to an error list that will be returned to the caller. This solution is not optimal and is only intended for educational purposes. Next, we create a manifest.json file that contains the meta-data for our bundle:

{
  "bundle.symbolic_name" : "spellcheckservice",
  "bundle.activator" : true
}

Note

In this example, the service interface and implementation are both contained in one bundle which exports the interface class. However, service implementations almost never need to be exported and in many use cases it is beneficial to provide the service interface and its implementation(s) in separate bundles. In such a scenario, clients of a service will only have a link-time dependency on the shared library providing the service interface (because of the out-of-line destructor) but not on any bundles containing service implementations. This often leads to bundles which do not export any symbols at all.

For an introduction how to compile our source code, see Example 1 - Service Event Listener.

After running the usTutorialDriver program we should make sure that the bundle from Example 1 is active. We can use the status shell command to get a list of all bundles, their state, and their bundle identifier number. If the Example 1 bundle is not active, we should start the bundle using the start command and the bundle’s identifier number or symbolic name that is displayed by the status command. Now we can start the spell checker service bundle by entering the start spellcheckservice command which will also trigger the starting of the dictionaryservice bundle containing the english dictionary:

CppMicroServices-build> bin/usTutorialDriver
> start eventlistener
Starting to listen for service events.
> start spellcheckservice
> status
Id | Symbolic Name        | State
-----------------------------------
 0 | system_bundle        | ACTIVE
 1 | eventlistener        | ACTIVE
 2 | dictionaryservice    | INSTALLED
 3 | frenchdictionary     | INSTALLED
 4 | dictionaryclient     | INSTALLED
 5 | dictionaryclient2    | INSTALLED
 6 | dictionaryclient3    | INSTALLED
 7 | spellcheckservice    | ACTIVE
 8 | spellcheckclient     | INSTALLED
>

To trigger the registration of the spell checker service from our bundle, we start the frenchdictionary using the start frenchdictionary command. If the bundle from Example 1 is still active, then we should see it print out the details of the service event it receives when our new bundle registers its spell checker service:

CppMicroServices-build> bin/usTutorialDriver
> start frenchdictionary
Ex1: Service of type IDictionaryService registered.
Ex1: Service of type ISpellCheckService registered.
>

We can experiment with our spell checker service’s dynamic availability by stopping the french dictionary service; when the service is stopped, the eventlistener bundle will print that our bundle is no longer offering its spell checker service. Likewise, when the french dictionary service comes back, so will our spell checker service. We create a client for our spell checker service in Example 7. To exit the usTutorialDriver program, we use the shutdown command.