Example 4 - Robust Dictionary Client BundleΒΆ

In Example 3, we create a simple client bundle for our dictionary service. The problem with that client was that it did not monitor the dynamic availability of the dictionary service, thus an error would occur if the dictionary service disappeared while the client was using it. In this example we create a client for the dictionary service that monitors the dynamic availability of the dictionary service. The result is a more robust client.

The functionality of the new dictionary client is essentially the same as the old client, it reads words from standard input and checks for their existence in the dictionary service. Our bundle uses its bundle context to register itself as a service event listener; monitoring service events allows the bundle to monitor the dynamic availability of the dictionary service. Our client uses the first dictionary service it finds. The source code for our bundle is as follows in a file called Activator.cpp:

#include "IDictionaryService.h"

#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 bundle activator that uses a dictionary service to check for
     * the proper spelling of a word by checking for its existence in the
     * dictionary. This bundle is more complex than the bundle in Example 3 because
     * it monitors the dynamic availability of the dictionary services. In other
     * words, if the service it is using departs, then it stops using it gracefully,
     * or if it needs a service and one arrives, then it starts using it
     * automatically. As before, the bundle uses the first service that it finds and
     * uses the calling thread of the Start() method to read words from standard
     * input. You can stop checking words by entering an empty line, but to start
     * checking words again you must unload and then load the bundle again.
     */
    class US_ABI_LOCAL Activator : public BundleActivator
    {

      public:
        Activator() : m_context(), m_dictionary(nullptr) {}

        /**
         * Implements BundleActivator::Start(). Adds itself as a listener for service
         * events, then queries for available dictionary services. If any
         * dictionaries are found it gets a reference to the first one available and
         * then starts its "word checking loop". If no dictionaries are found, then
         * it just goes directly into its "word checking loop", but it will not be
         * able to check any words until a dictionary service arrives; any arriving
         * dictionary service will be automatically used by the client if a
         * dictionary is not already in use. Once it has dictionary, it reads words
         * from standard input and checks for their existence in the dictionary that
         * it is using.
         *
         * \note It is very bad practice to use the calling thread to perform a
         *       lengthy process like this; this is only done for the purpose of
         *       the tutorial.
         *
         * @param context the bundle context for this bundle.
         */
        void
        Start(BundleContext context)
        {
            m_context = context;

            {
                // Use your favorite thread library to synchronize member
                // variable access within this scope while registering
                // the service listener and performing our initial
                // dictionary service lookup since we
                // don't want to receive service events when looking up the
                // dictionary service, if one exists.
                // MutexLocker lock(&m_mutex);

                // Listen for events pertaining to dictionary services.
                m_context.AddServiceListener(std::bind(&Activator::ServiceChanged, this, std::placeholders::_1),
                                             std::string("(&(") + Constants::OBJECTCLASS + "="
                                                 + us_service_interface_iid<IDictionaryService>() + ")"
                                                 + "(Language=*))");

                // Query for any service references matching any language.
                std::vector<ServiceReference<IDictionaryService>> refs
                    = context.GetServiceReferences<IDictionaryService>("(Language=*)");

                // If we found any dictionary services, then just get
                // a reference to the first one so we can use it.
                if (!refs.empty())
                {
                    m_ref = refs.front();
                    m_dictionary = m_context.GetService(m_ref);
                }
            }

            std::cout << "Enter a blank line to exit." << std::endl;

            // Loop endlessly until the user enters a blank line
            while (std::cin)
            {
                // Ask the user to enter a word.
                std::cout << "Enter word: ";

                std::string word;
                std::getline(std::cin, word);

                // If the user entered a blank line, then
                // exit the loop.
                if (word.empty())
                {
                    break;
                }
                // If there is no dictionary, then say so.
                else if (m_dictionary == nullptr)
                {
                    std::cout << "No dictionary available." << std::endl;
                }
                // Otherwise print whether the word is correct or not.
                else if (m_dictionary->CheckWord(word))
                {
                    std::cout << "Correct." << std::endl;
                }
                else
                {
                    std::cout << "Incorrect." << std::endl;
                }
            }
        }

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

        /**
         * Implements ServiceListener.serviceChanged(). Checks to see if the service
         * we are using is leaving or tries to get a service if we need one.
         *
         * @param event the fired service event.
         */
        void
        ServiceChanged(ServiceEvent const& event)
        {
            // Use your favorite thread library to synchronize this
            // method with the Start() method.
            // MutexLocker lock(&m_mutex);

            // If a dictionary service was registered, see if we
            // need one. If so, get a reference to it.
            if (event.GetType() == ServiceEvent::SERVICE_REGISTERED)
            {
                if (!m_ref)
                {
                    // Get a reference to the service object.
                    m_ref = event.GetServiceReference();
                    m_dictionary = m_context.GetService(m_ref);
                }
            }
            // If a dictionary service was unregistered, see if it
            // was the one we were using. If so, unget the service
            // and try to query to get another one.
            else if (event.GetType() == ServiceEvent::SERVICE_UNREGISTERING)
            {
                if (event.GetServiceReference() == m_ref)
                {
                    // Unget service object and null references.
                    m_ref = nullptr;
                    m_dictionary.reset();

                    // Query to see if we can get another service.
                    std::vector<ServiceReference<IDictionaryService>> refs;
                    try
                    {
                        refs = m_context.GetServiceReferences<IDictionaryService>("(Language=*)");
                    }
                    catch (std::invalid_argument const& e)
                    {
                        std::cout << e.what() << std::endl;
                    }

                    if (!refs.empty())
                    {
                        // Get a reference to the first service object.
                        m_ref = refs.front();
                        m_dictionary = m_context.GetService(m_ref);
                    }
                }
            }
        }

      private:
        // Bundle context
        BundleContext m_context;

        // The service reference being used
        ServiceReference<IDictionaryService> m_ref;

        // The service object being used
        std::shared_ptr<IDictionaryService> m_dictionary;
    };
} // namespace

CPPMICROSERVICES_EXPORT_BUNDLE_ACTIVATOR(Activator)

The client listens for service events indicating the arrival or departure of dictionary services. If a new dictionary service arrives, the bundle will start using that service if and only if it currently does not have a dictionary service. If an existing dictionary service disappears, the bundle will check to see if the disappearing service is the one it is using; if it is it stops using it and tries to query for another dictionary service, otherwise it ignores the event.

Like normal, we must create a manifest.json file that contains the meta-data for our bundle:

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

As in Example 3, we must link our bundle to the dictionaryservice bundle:

set(_srcs Activator.cpp)

set(dictionaryclient2_DEPENDS dictionaryservice)
CreateTutorial(dictionaryclient2 ${_srcs})

After running the usTutorialDriver executable, and starting the event listener bundle, we can use the start dictionaryclient2 command to start our robust dictionary client bundle:

CppMicroServices-debug> bin/usTutorialDriver
> start eventlistener
Starting to listen for service events.
> start dictionaryclient2
Ex1: Service of type IDictionaryService registered.
Enter a blank line to exit.
Enter word:

The above command starts the bundle and it will use the main thread to prompt us for words. Enter one word at a time to check the words and enter a blank line to stop checking words. To reload the bundle, we must first use the stop dictionaryclient2 command to stop the bundle, then the start dictionaryclient2 command to re-start it. To test the dictionary service, enter any of the words in the dictionary (e.g., “welcome”, “to”, “the”, “micro”, “services”, “tutorial”) or any word not in the dictionary.

Since this client monitors the dynamic availability of the dictionary service, it is robust in the face of sudden departures of the dictionary service. Further, when a dictionary service arrives, it automatically gets the service if it needs it and continues to function. These capabilities are a little difficult to demonstrate since we are using a simple single-threaded approach, but in a multi-threaded or GUI-oriented application this robustness is very useful.