CTK  0.1.0
The Common Toolkit is a community effort to provide support code for medical image analysis, surgical navigation, and related projects.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
Event Admin

The Event Admin Service Specification, part of the OSGi Compendium specification, defines a general inter-plug-in communication mechanism. The communication conforms to the popular publish/subscribe paradigm and can be performed in a synchronous or asysnchronous manner.

The main components in a publish/subscribe communication are:

Events are composed of two attributes:

Read the EventAdmin Service Specifications (Chapter 113) for an in-depth explanation.

Creating an Event Publisher

An event publisher can either be a simple C++ class that creates events and sends them using the ctkEventAdmin service interface or a Qt signal which is registered as a "publisher" using the ctkEventAdmin service interface.

Using a simple C++ class

The publisher is a function which creates a ctkEvent object and sends it by using the ctkEventAdmin service interface.

void reportGenerated(const Report& report, ctkPluginContext* context)
{
if (ref)
{
ctkEventAdmin* eventAdmin = context->getService<ctkEventAdmin>(ref);
ctkDictionary properties;
properties["title"] = report.getTitle();
properties["path"] = report.getAbsolutePath();
properties["time"] = QTime::currentTime();
ctkEvent reportGeneratedEvent("com/acme/reportgenerator/GENERATED", properties);
eventAdmin->sendEvent(reportGeneratedEvent);
}
}

The ctkEventAdmin::sendEvent() method sends the ctkEvent object synchronously. To send it asynchronously, use the ctkEventAdmin::postEvent() method, such as:

ctkEvent reportGeneratedEvent("com/acme/reportgenerator/GENERATED", properties);
eventAdmin->postEvent(reportGeneratedEvent);

Synchronous event delivery is significantly more expensive than asynchronous delivery. Even for synchronous delivery event notifications could be handled in a separate thread (depending on the EventAdmin implementation). This implies that sendEvent() callers should generally not hold any locks when calling this method. Asynchronous delivery should be preferred over the synchronous delivery.

Using a Qt signal

Using a Qt signal to publish an event requires declaring a signal and registering (publishing) it with the Event Admin:

signals:
void reportGeneratedSignal(const ctkDictionary&);

Register the signal using a specific topic (emitting the signal will always send ctkEvent objects with this topic as EVENT_TOPIC property):

ReportManager(ctkPluginContext* context)
{
if (ref)
{
ctkEventAdmin* eventAdmin = context->getService<ctkEventAdmin>(ref);
// Using Qt::DirectConnection is equivalent to ctkEventAdmin::sendEvent()
eventAdmin->publishSignal(this, SIGNAL(reportGeneratedSignal(ctkDictionary)),
"com/acme/reportgenerator/GENERATED", Qt::DirectConnection);
}
}

Emitting the signal will automatically create a ctkEvent object, sending it synchronuously or asynchronuously, depending on the Qt::ConnectionType used when publishing the signal.

void reportGenerated(const Report& report)
{
ctkDictionary properties;
properties["title"] = report.getTitle();
properties["path"] = report.getAbsolutePath();
properties["time"] = QTime::currentTime();
emit reportGeneratedSignal(properties);
}

Comparison

The act of sending an event is simplified by using a Qt signal, after it was registered with the Event Admin. However, the Qt signal approach is less performant since the signal emission needs to go through the Qt meta object system. Further, the signal is tied to a specific event topic.

Creating and registering an Event Handler

An event handler can either be a class implementing the ctkEventHandler interface which is registered as a service object or a Qt slot which is registered with the Event Admin (subscribed to certain topics).

Event handlers should not spend too long in the event handling method. This will prevent other handlers from being notified. Long running operations should be executed in their own thread.

Note that in general, your event handling code will be called from a separate thread.

Event Handler as a Service

Create an event handler by implementing the ctkEventHandler interface:

class ReportEventHandler : public QObject, public ctkEventHandler
{
Q_OBJECT
Q_INTERFACES(ctkEventHandler)
public:
void handleEvent(const ctkEvent& event)
{
QString reportTitle = event.getProperty("title").toString();
QString reportPath = event.getProperty("path").toString();
// sendReportByEmail(reportTitle, reportPath);
qDebug() << "title:" << reportTitle << "path:" << reportPath;
}
};

To receive event notifications, the event handler must be registered as a service under the ctkEventHandler interface. When registering the service, a QString or QStringList property named EVENT_TOPIC must be specified. This property describes the list of topics in which the event handler is interested. For example:

ReportEventHandler eventHandler;
props[ctkEventConstants::EVENT_TOPIC] = "com/acme/reportgenerator/GENERATED";
pluginContext->registerService<ctkEventHandler>(&eventHandler, props);

It is possible to use '*' as a wildcard in the final character of the EVENT_TOPIC:

props[ctkEventConstants::EVENT_TOPIC] = "com/acme/reportgenerator/*";
pluginContext->registerService<ctkEventHandler>(&eventHandler, props);

Finally, it is possible to specify an additional EVENT_FILTER property to filter event notifications. The filter expression follows the normal LDAP syntax:

props[ctkEventConstants::EVENT_TOPIC] = "com/acme/reportgenerator/GENERATED";
props[ctkEventConstants::EVENT_FILTER] = "(title=samplereport)";
pluginContext->registerService<ctkEventHandler>(&eventHandler, props);

Event Handler as a Qt slot

Every Qt slot taking a ctkEvent object as an argument can be subscribed to receive event notifications. For example, a slot like

class ReportEventHandlerUsingSlots : public QObject
{
Q_OBJECT
public slots:
void handleEvent(const ctkEvent& event)
{
QString reportTitle = event.getProperty("title").toString();
QString reportPath = event.getProperty("path").toString();
// sendReportByEmail(reportTitle, reportPath);
qDebug() << "[slot] title:" << reportTitle << "path:" << reportPath;
}
};

can be subscribed to receive events like

ReportEventHandlerUsingSlots eventHandlerUsingSlots;
ctkDictionary propsForSlot;
propsForSlot[ctkEventConstants::EVENT_TOPIC] = "com/acme/reportgenerator/*";
ctkServiceReference ref = pluginContext->getServiceReference<ctkEventAdmin>();
if (ref)
{
ctkEventAdmin* eventAdmin = pluginContext->getService<ctkEventAdmin>(ref);
eventAdmin->subscribeSlot(&eventHandlerUsingSlots, SLOT(handleEvent(ctkEvent)), propsForSlot);
}

You can use the same expressions for EVENT_TOPIC and EVENT_FILTER as in the examples above for registering the event handler as a service object implementing ctkEventHandler.

Using Qt slots as Event Handlers will makes it easy to ensure that the event handling code is executed in the receiver's thread (the default connection type is Qt::AutoConnection).

Comparison

Registering an event handler using either the ctkEventHandler interface or a Qt slot involves approximately the same amount of code. However, using slots will be less performant (which might be neglectable, depending on your use case) but the code will be automatically synchronized with the receiver thread.

Further, subscribing slots means that you require a registered Event Admin service implementation. The ctkEventHandler approach does not need to know anything about the Event Admin, since you register your handler as a service object in the framework.