15 Using Cache Events (C++)

You can use map event listeners to receive cache events and events from any class in Coherence that implements the ObservableMap interface.

This chapter includes the following sections:

15.1 Overview of Map Events (C++)

The event model is comprised of an EventListener interface that all listeners must extend. Coherence provides a MapListener interface, which allows application logic to receive events when data in a Coherence cache is added, modified or removed.

An application object that implements the MapListener interface can sign up for events from any Coherence cache or class that implements the ObservableMap interface, simply by passing an instance of the application's MapListener implementation to an addMapListener() method.

The MapEvent object that is passed to the MapListener carries all of the necessary information about the event that has occurred, including the source (ObservableMap) that raised the event, the identity (key) that the event is related to, what the action was against that identity (insert, update or delete), what the old value was and what the new value is.

15.2 Caches and Classes that Support Events

All Coherence caches implement the ObservableMap interface, which allows an application to receive cache events. Any cache can receive events, regardless of whether that cache is local, partitioned, near, replicated, using read-through, write-through, write-behind, overflow, disk storage, and so on.

Note:

Regardless of the cache topology and the number of servers, and even if the modifications are being made by other servers, the events are delivered to the application's listeners.

In addition to the Coherence caches (those objects obtained through a Coherence cache factory), several other supporting classes in Coherence also implement the ObservableMap interface:

  • ObservableHashMap

  • LocalCache

  • OverflowMap

  • NearCache

  • ReadWriteBackingMap

  • AbstractSerializationCache, SerializationCache, and SerializationPagedCache

  • WrapperObservableMap, WrapperConcurrentMap, and WrapperNamedCache

For a full list of published implementing classes, see the Coherence API for ObservableMap.

15.3 Signing Up for all Events

To sign up for events, pass an object that implements the MapListener interface to an addMapListener method on ObservableMap.

For example:

virtual void addKeyListener(MapListener::Handle hListener, Object::View vKey, bool fLite) = 0;
virtual void removeKeyListener(MapListener::Handle hListener, Object::View vKey) = 0;
virtual void addFilterListener(MapListener::Handle hListener, Filter::View vFilter = NULL, bool fLite = false) = 0;
virtual void removeFilterListener(MapListener::Handle hListener, Filter::View vFilter = NULL) = 0;

Let's create an example MapListener implementation:

#include "coherence/util/MapEvent.hpp"
#include "coherence/util/MapListener.hpp"

#include <iostream>

using coherence::util::MapEvent;
using coherence::util::MapListener;
using namespace std;

/**
* A MapListener implementation that prints each event as it receives
* them.
*/
class EventPrinter 
    : public class_spec<EventPrinter,
        extends<Object>,
        implements<MapListener> >
    {    
    friend class factory<EventPrinter>;

    public:
        virtual void entryInserted(MapEventView vEvent)
            {
            cout << vEvent << endl;
            }

        virtual void entryUpdated(MapEventView vEvent)
            {
            cout << vEvent << endl;
            }

        virtual void entryDeleted(MapEventView vEvent)
            {
            cout << vEvent << endl;
            }
    };

Using this implementation simplifies printing all events from any given cache (since all caches implement the ObservableMap interface):

NamedCache::Handle hCache;
...
hCache->addFilterListener(EventPrinter::create());

Of course, to be able to later remove the listener, it is necessary to hold on to a reference to the listener:

MapListener::Handle hListener = EventPrinter::create();
hCache->addFilterListener(hListener);
m_hListener = hListener; // store the listener in a member field

Later, to remove the listener:

MapListener::Handle hListener = m_hListener;
if (hListener != NULL)
    {
    hCache->removeFilterListener(hListener);
    m_hListener = NULL; // clean up the listener field
    }

Each add*Listener method on the ObservableMap interface has a corresponding remove*Listener method. To remove a listener, use the remove*Listener method that corresponds to the add*Listener method that was used to add the listener.

15.4 Using a Multiplexing Map Listener

The MultiplexingMapListener class routes all events to a single method for handling. The following example illustrates a simple EventPrinter class:
#include "coherence/util/MultiplexingMapListener.hpp"

#include <iostream>

using coherence::util::MultiplexingMapListener;


class EventPrinter 
    : public class_spec<EventPrinter,
        extends<MultiplexingMapListener> >
    {
    public:
        virtual void onMapEvent(MapEventView vEvent)
            {
            std::cout << vEvent << std::endl;
            }
    };

15.5 Configuring a MapListener for a Cache

You can register a listener on a cache using the <listener> element in the cache configuration. If configured, then Coherence automatically adds the listener when it configures the cache. Registering a listener in the configuration is useful when a listener should always be on a particular cache.

15.6 Signing Up for Events on Specific Identities

You can sign up for events that occur against specific identities (keys). The following code in prints all events that occur against the Integer key 5:
hCache->addKeyListener(EventPrinter::create(), Integer32::create(5), false);

The following code only triggers an event when the Integer key 5 is inserted or updated:

for (int32_t i = 0; i < 10; ++i)
    {
    Integer32::View vKey   = Integer32::create(i);
    Integer32::View vValue = vKey;
    hCache->put(vKey, vValue);
    }

15.7 Filtering Events

You can use a filter to listen for specific events. In the following example, a listener is added to the cache with a filter that allows the listener to only receive delete events.
// Filters used with partitioned caches must implement coherence::io::pof::PortableObject

#include "coherence/io/pof/PofReader.hpp"
#include "coherence/io/pof/PofWriter.hpp"
#include "coherence/io/pof/PortableObject.hpp"
#include "coherence/util/Filter.hpp"
#include "coherence/util/MapEvent.hpp"

using coherence::io::pof::PofReader;
using coherence::io::pof::PofWriter;
using coherence::io::pof::PortableObject;
using coherence::util::Filter;
using coherence::util::MapEvent;

class DeletedFilter
    : public class_spec<DeletedFilter,
        extends<Object>,
        implements<Filter, PortableObject> >
    {
    public:
        // Filter interface        virtual bool evaluate(Object::View v) const
            {
            MapEvent::View vEvt = cast<MapEvent::View>(v);
            return MapEvent::entry_deleted == vEvt->getId();
            }

        // PortableObject interface        virtual void readExternal(PofReader::Handle hIn)
            {
            }

        virtual void writeExternal(PofWriter::Handle hOut) const
            {
            }
    };

hCache->addFilterListener(EventPrinter::create(), DeletedFilter::create(), false);

For example, if the following sequence of calls were made:

cache::put(String::create("hello"), String::create("world"));
cache::put(String::create("hello"), String::create("again"));
cache::remove(String::create("hello"));

The result would be:

CacheEvent{LocalCache deleted: key=hello, value=again}

See Listening to Queries.

Filtering Events Versus Filtering Cached Data

When building a Filter for querying, the object that is passed to the evaluate method of the Filter is a value from the cache, or, if the Filter implements the EntryFilter interface, the entire Map::Entry from the cache. When building a Filter for filtering events for a MapListener, the object that is passed to the evaluate method of the Filter is always of type MapEvent.

15.8 Using Lite Events

You can save resources by using lite events if an application does not require the old and the new value to be included in the event. By default, Coherence provides both the old and the new value as part of an event. Consider the following example:
MapListener::Handle hListener = EventPrinter::create();
// add listener with the default"lite" value of falsehCache->addFilterListener(hListener);

// insert a 1KB value
String::View vKey = String::create("test");
hCache->put(vKey, Array<octet_t>::create(1024));

// update with a 2KB value
hCache->put(vKey, Array<octet_t>::create(2048));

// remove the value
hCache->remove(vKey);

When the above code is run, the insert event carries the new 1KB value, the update event carries both the old 1KB value and the new 2KB value and the remove event carries the removed 2KB value.

When adding a listener, you can request lite events by using either the addFilterListener or the addKeyListener method that takes an additional boolean fLite parameter. In the above example, the only change would be:

cache->addFilterListener(hListener, (Filter::View) NULL, true);

Note:

A lite event's old value and new value may be NULL. However, even if you request lite events, the old and the new value might be included if there is no additional cost to generate and deliver the event. In other words, requesting that a MapListener receive lite events is simply a hint to the system that the MapListener does not require knowledge of the old and new values for the event.

15.9 Listening to Queries

Coherence caches support querying by any criteria. When an application queries for data from a cache, the result is a point-in-time snapshot, either as a set of identities (keySet) or a set of identity/value pairs (entrySet). The mechanism for determining the contents of the resulting set is referred to as filtering, and it allows an application developer to construct queries of arbitrary complexity using a rich set of out-of-the-box filters (for example, equals, less-than, like, between, and so on), or to provide their own custom filters (for example, XPath).

The same filters that are used to query a cache are used to listen to events from a cache. For example, in a trading system it is possible to query for all open Order objects for a particular trader.

Note:

The following example uses the coherence::util::extractor::ReflectionExtractor class. While the C++ client does not support reflection, ReflectionExtractor can be used for queries which are executed in the cluster. In this case, the ReflectionExtractor simply passes the necessary extraction information to the cluster to perform the query. In cases where the ReflectionExtractor would extract the data on the client, such as the ContinuousQueryCache when caching values locally, the use of the ReflectionExtractor is not supported. For these cases, you must provide a custom extractor.

NamedCache::Handle hMapTrades = ...
Filter::Handle hFilter = AndFilter::create(
        EqualsFilter::create(ReflectionExtractor::create("getTrader"), vTraderId),
        EqualsFilter::create(ReflectionExtractor::create("getStatus"), Status::OPEN));
Set::View vSetOpenTrades = hMapTrades->entrySet(hFilter);

To receive notifications of new trades being opened for that trader, closed by that trader or reassigned to or from another trader, the application can use the same filter:

// receive events for all trade IDs that this trader is interested in
hMapTrades->addFilterListener(hListener, MapEventFilter::create(hFilter), true);

The MapEventFilter converts a query filter into an event filter.

Note:

Filtering events versus filtering cached data: When building a Filter for querying, the object that is passed to the evaluate method of the Filter is a value from the cache, or, if the Filter implements the EntryFilter interface, the entire Map::Entry from the cache. When building a Filter for filtering events for a MapListener, the object that is passed to the evaluate method of the Filter is always be of type MapEvent.

The MapEventFilter converts a Filter that is used to do a query into a Filter that is used to filter events for a MapListener. In other words, the MapEventFilter is constructed from a Filter that queries a cache, and the resulting MapEventFilter is a filter that evaluates MapEvent objects by converting them into the objects that a query Filter would expect.

The MapEventFilter has several very powerful options, allowing an application listener to receive only the events that it is specifically interested in. More importantly for scalability and performance, only the desired events have to be communicated over the network, and they are communicated only to the servers and clients that have expressed interest in those specific events. For example:

// receive all events for all trades that this trader is interested in
int32_t nMask = MapEventFilter::e_all;
hMapTrades->addFilterListener(hListener, MapEventFilter::create(nMask, hFilter), true);

// receive events for all this trader's trades that are closed or
// re-assigned to a different trader
nMask = MapEventFilter::e_updated_left | MapEventFilter::e_deleted;
hMapTrades->addFilterListener(hListener, MapEventFilter::create(nMask, hFilter), true);

// receive events for all trades as they are assigned to this trader
nMask = MapEventFilter::e_inserted | MapEventFilter::e_updated_entered;
hMapTrades->addFilterListener(hListener, MapEventFilter::create(nMask, hFilter), true);

// receive events only for new trades assigned to this trader
nMask = MapEventFilter::e_inserted;
hMapTrades->addFilterListener(hListener, MapEventFilter::create(nMask, hFilter), true);

15.10 Using Synthetic Events

An application can listen for synthetic events, which originate from operations within a cache. Synthetic events are different than client events.

Events usually reflect the changes being made to a cache. For example, one server is modifying one entry in a cache; while, another server is adding several items to a cache; while, a third server is removing an item from the same cache; while, fifty threads on each server in the cluster is accessing data from the same cache. All the modifying actions produce events that any server within the cluster can choose to receive. These actions are referred to as client actions and the events as being dispatched to clients, even though the clients in this case are actually servers. This is a natural concept in a true peer-to-peer architecture, such as a Coherence cluster: Each and every peer is both a client and a server, both consuming services from its peers and providing services to its peers. In a typical Java Enterprise application, a peer is an application server instance that is acting as a container for the application, and the client is that part of the application that is directly accessing and modifying the caches and listening to events from the caches.

Some events originate from within a cache itself. There are many examples, but the most common cases are:

  • When entries automatically expire from a cache;

  • When entries are evicted from a cache because the maximum size of the cache has been reached;

  • When entries are transparently added to a cache as the result of a Read-Through operation;

  • When entries in a cache are transparently updated as the result of a Read-Ahead or Refresh-Ahead operation.

Each of these represents a modification, but the modifications represent natural (and typically automatic) operations from within a cache. These events are referred to as synthetic events.

When necessary, an application can differentiate between client-induced and synthetic events simply by asking the event if it is synthetic. This information is carried on a sub-class of the MapEvent, called CacheEvent. Using the previous EventPrinter example, it is possible to print only the synthetic events:

class EventPrinter
    : public class_spec<EventPrinter,
        extends<MultiplexingMapListener> >
    {
    friend class factory<EventPrinter>;

    public:
        void onMapEvent(MapEvent::View vEvt)
            {
            if (instanceof<CacheEvent::View>(vEvt) &&
                (cast<CacheEvent::View>(vEvt)->isSynthetic()))
                {
                std::cout << vEvt;
                }
            }
    };

For more information on this feature, see the API documentation for CacheEvent.

15.11 Using Backing Map Events

For some advanced use cases, you can listen to events on the map behind a service. Replication, partitioning and other approaches to managing data in a distributed environment are all distribution services. The data structure that actually manages the data for a service is called a backing map.

Backing maps are configurable. If all the data for a particular cache should be kept in object form on the heap, then use an unlimited and non-expiring LocalCache (or a SafeHashMap if statistics are not required). If only a small number of items should be kept in memory, use a LocalCache. If data are to be read on demand from a database, then use a ReadWriteBackingMap (which knows how to read and write through an application's DAO implementation), and in turn give the ReadWriteBackingMap a backing map such as a SafeHashMap or a LocalCache to store its data in.

Some backing maps are observable. The events coming from these backing maps are not usually of direct interest to the application. Instead, Coherence translates them into actions that must be taken (by Coherence) to keep data synchronized and properly backed up, and it also translates them when appropriate into clustered events that are delivered throughout the cluster as requested by application listeners. For example, if a partitioned cache has a LocalCache as its backing map, and the local cache expires an entry, that event causes Coherence to expire all of the backup copies of that entry. Furthermore, if any listeners have been registered on the partitioned cache, and if the event matches their event filter(s), then that event is delivered to those listeners on the servers where those listeners were registered.

In some advanced use cases, an application must process events on the server where the data are being maintained, and it must do so on the structure (backing map) that is actually managing the data. In these cases, if the backing map is an observable map, a listener can be configured on the backing map or one can be programmatically added to the backing map. (If the backing map is not observable, it can be made observable by wrapping it in an WrapperObservableMap.)

15.12 Using Synchronous Event Listeners

Some events are delivered asynchronously, so that application listeners do not disrupt the cache services that are generating the events. In some rare scenarios, asynchronous delivery can cause ambiguity of the ordering of events compared to the results of ongoing operations.
A MapListener implementation can use the SynchronousListener marker interface to guarantee that the cache API operations and the events are ordered as if the local view of the clustered system were single-threaded.

One example in Coherence itself that uses synchronous listeners is the Near Cache, which can use events to invalidate locally cached data.