Medical Imaging Interaction Toolkit  2023.12.00
Medical Imaging Interaction Toolkit
Example 4 - Robust Dictionary Client Module

In Example 3, we create a simple client module 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 module uses its module context to register itself as a service event listener; monitoring service events allows the module to monitor the dynamic availability of the dictionary service. Our client uses the first dictionary service it finds. The source code for our module is as follows in a file called Activator.cpp:

#include "IDictionaryService.h"
namespace {
class US_ABI_LOCAL Activator : public ModuleActivator
{
public:
Activator()
: m_context(nullptr)
, m_dictionary(nullptr)
{}
void Load(ModuleContext *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(this, &Activator::ServiceChanged,
std::string("(&(") + ServiceConstants::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;
}
}
}
void Unload(ModuleContext* /*context*/)
{
// NOTE: The service is automatically released.
}
void ServiceChanged(const ServiceEvent event)
{
// Use your favorite thread library to synchronize this
// method with the Load() 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::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::UNREGISTERING)
{
if (event.GetServiceReference() == m_ref)
{
// Unget service object and null references.
m_context->UngetService(m_ref);
m_ref = 0;
m_dictionary = nullptr;
// Query to see if we can get another service.
std::vector<ServiceReference<IDictionaryService> > refs;
try
{
refs = m_context->GetServiceReferences<IDictionaryService>("(Language=*)");
}
catch (const std::invalid_argument& 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:
// Module context
ModuleContext* m_context;
// The service reference being used
ServiceReference<IDictionaryService> m_ref;
// The service object being used
IDictionaryService* m_dictionary;
};
}

The client listens for service events indicating the arrival or departure of dictionary services. If a new dictionary service arrives, the module will start using that service if and only if it currently does not have a dictionary service. If an existing dictionary service disappears, the module 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.

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

set(_srcs Activator.cpp)
usFunctionGenerateModuleInit(_srcs)
set(dictionaryclient2_DEPENDS dictionaryservice)
CreateExample(dictionaryclient2 ${_srcs})

After running the usCoreExamplesDriver executable, and loading the event listener module, we can use the l dictionaryclient2 command to load our robust dictionary client module:

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

The above command loads the module and its dependencies (the dictionaryservice module) in a single step. When we load the module, 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 module, we must use the s command to get the module identifier number for the module and first use the u <id> command to unload the module, then the l <id> command to re-load 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 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.

Next: Example 5 - Service Tracker Dictionary Client Module

Previous: Example 3 - Dictionary Client Module

usModuleActivator.h
ModuleContext::AddServiceListener
void AddServiceListener(const ServiceListener &delegate, const std::string &filter=std::string())
ModuleContext
Definition: usModuleContext.h:91
US_USE_NAMESPACE
#define US_USE_NAMESPACE
Definition: usGlobalConfig.h:75
us::ServiceConstants::OBJECTCLASS
const US_Core_EXPORT std::string & OBJECTCLASS()
US_ABI_LOCAL
#define US_ABI_LOCAL
Definition: usGlobalConfig.h:117
US_EXPORT_MODULE_ACTIVATOR
#define US_EXPORT_MODULE_ACTIVATOR(_activator_type)
Export a module activator class.
Definition: usModuleActivator.h:119
usModuleContext.h
ModuleContext::GetServiceReferences
std::vector< ServiceReferenceU > GetServiceReferences(const std::string &clazz, const std::string &filter=std::string())