Distributed Object Events
Listening for Map Events
You can listen to map-level or entry-based events using the listeners
provided by the Hazelcast’s eventing framework. To listen to these events,
implement a MapListener
sub-interface.
A map-level event is fired as a result of a map-level operation, e.g.,
IMap.clear()
or IMap.evictAll()
.
An entry-based event is fired after the operations that affect a
specific entry, e.g., IMap.remove()
or IMap.evict()
.
Here are the listeners for map-level and entry-based events:
-
EntryAddedListener
: It is notified when an entry is added to the map. -
EntryEvictedListener
: It is notified when an entry is removed from the map due to size-based eviction. See the Map Eviction section for more information. -
EntryExpiredListener
: It is notified when an entry is removed from the map due to expiration-based eviction (happens when time-to-live and/or maximum idle seconds are configured for the entries). If your listener implements both this one andEntryEvictedListener
together, the listener may receive both expiration and eviction events for the same entry; this is because, size-based eviction removes entries regardless of whether entries are expired or not. -
EntryLoadedListener
: It is notified when an entry is loaded by aMapLoader
implementation. -
EntryMergedListener
: It is notified when a WAN-replicated entry is merged. -
EntryRemovedListener
: It is notified when an entry is directly removed from the map, for example using IMap’sremove()
method or the RESTDELETE
call. -
EntryUpdatedListener
: It is notified when an entry is updated. -
EventLostListener
: It is notified when events are lost. -
MapClearedListener
: It is notified when all the entries of a map are removed using IMap’sclear()
method. -
MapEvictedListener: It is notified when all the entries of a map are removed using IMap’s `evictAll()
method. -
MapPartitionLostListener
: It is notified when the owner and all backups of a partition is lost for a specific map. See the Listening for Lost Map Partitions section for details.
Catching a Map Event
To catch an event, you should explicitly
implement a corresponding sub-interface of a MapListener
,
such as EntryAddedListener
or MapClearedListener
.
The EntryListener interface still can be implemented (we kept
it for backward compatibility reasons). However, if you need to listen to a
different event, one that is not available in the EntryListener interface, you should also
implement a relevant MapListener sub-interface.
|
Let’s take a look at the following class example.
public class Listen {
public static void main( String[] args ) {
HazelcastInstance hz = HazelcastClient.newHazelcastClient();
IMap<String, String> map = hz.getMap( "somemap" );
map.addEntryListener( new MyEntryListener(), true );
System.out.println( "EntryListener registered" );
}
static class MyEntryListener implements
EntryAddedListener<String, String>,
EntryRemovedListener<String, String>,
EntryUpdatedListener<String, String>,
EntryEvictedListener<String, String>,
EntryLoadedListener<String,String>,
MapEvictedListener,
MapClearedListener {
@Override
public void entryAdded( EntryEvent<String, String> event ) {
System.out.println( "Entry Added:" + event );
}
@Override
public void entryRemoved( EntryEvent<String, String> event ) {
System.out.println( "Entry Removed:" + event );
}
@Override
public void entryUpdated( EntryEvent<String, String> event ) {
System.out.println( "Entry Updated:" + event );
}
@Override
public void entryEvicted( EntryEvent<String, String> event ) {
System.out.println( "Entry Evicted:" + event );
}
@Override
public void entryLoaded( EntryEvent<String, String> event ) {
System.out.println( "Entry Loaded:" + event );
}
@Override
public void mapEvicted( MapEvent event ) {
System.out.println( "Map Evicted:" + event );
}
@Override
public void mapCleared( MapEvent event ) {
System.out.println( "Map Cleared:" + event );
}
}
}
Now, let’s perform some modifications on the map entries using the following example code.
public class ModifyMap {
public static void main( String[] args ) {
HazelcastInstance hz = HazelcastClient.newHazelcastClient();
IMap<String, String> map = hz.getMap( "somemap");
String key = "" + System.nanoTime();
String value = "1";
map.put( key, value );
map.put( key, "2" );
map.delete( key );
}
}
If you execute the Listen
class and then the Modify
class, you get the following output
produced by the Listen
class.
Entry Added:EntryEvent{entryEventType=ADDED, member=Member [192.168.1.100]]:5702
- ffedb655-bbad-43ea-aee8-d429d37ce528, name='somemap', key=11455268066242,
oldValue=null, value=1, mergingValue=null}
Entry Updated:EntryEvent{entryEventType=UPDATED, member=Member [192.168.1.100]]:5702
- ffedb655-bbad-43ea-aee8-d429d37ce528, name='somemap', key=11455268066242,
oldValue=1, value=2, mergingValue=null}
Entry Removed:EntryEvent{entryEventType=REMOVED, member=Member [192.168.1.100]]:5702
- ffedb655-bbad-43ea-aee8-d429d37ce528, name='somemap', key=11455268066242,
oldValue=null, value=null, mergingValue=null}
Please note that the method IMap.clear() does not fire an
"EntryRemoved" event, but fires a "MapCleared" event.
|
Listeners have to offload all blocking operations to another thread (pool). |
Listening for Lost Map Partitions
You can listen to MapPartitionLostEvent
instances by registering an implementation
of MapPartitionLostListener
, which is also a sub-interface of MapListener
.
Let’s consider the following example code:
public class ListenMapPartitionLostEvents {
public static void main(String[] args) {
Config config = new Config();
// keeps its data if a single node crashes
config.getMapConfig("map").setBackupCount(1);
HazelcastInstance instance = HazelcastInstanceFactory.newHazelcastInstance(config);
IMap<Object, Object> map = instance.getMap("map");
map.put(0, 0);
map.addPartitionLostListener(new MapPartitionLostListener() {
@Override
public void partitionLost(MapPartitionLostEvent event) {
System.out.println(event);
}
});
}
}
Within this example code, a MapPartitionLostListener
implementation is registered to a map
that is configured with one backup. For this particular map and any of the partitions in the
system, if the partition owner member and its first backup member crash simultaneously, the
given MapPartitionLostListener
receives a
corresponding MapPartitionLostEvent
. If only a single member crashes in the cluster,
there is no MapPartitionLostEvent
fired for this map since backups for the partitions
owned by the crashed member are kept on other members.
See the Listening for Partition Lost Events section for more information about partition lost detection and partition lost events.
Registering Map Listeners
After you create your listener class, you can configure your cluster
to include map listeners using the method addEntryListener
(as you
can see in the example Listen
class above). Below is the related portion
from this code, showing how to register a map listener.
HazelcastInstance hz = Hazelcast.newHazelcastInstance();
IMap<String, String> map = hz.getMap( "somemap" );
map.addEntryListener( new MyEntryListener(), true );
With the above approach, there is the possibility of missing events between the creation of the instance and registering the listener. To overcome this race condition, Hazelcast allows you to register listeners in configuration. You can register listeners using declarative, programmatic, or Spring configuration, as shown below.
The following is an example programmatic configuration.
mapConfig.addEntryListenerConfig(
new EntryListenerConfig( "com.yourpackage.MyEntryListener",
false, false ) );
The following is an example of the equivalent declarative configuration.
<hazelcast>
...
<map name="somemap">
<entry-listeners>
<entry-listener include-value="false" local="false">
com.yourpackage.MyEntryListener
</entry-listener>
</entry-listeners>
</map>
...
</hazelcast>
hazelcast:
map:
somemap:
entry-listeners:
- class-name: com.your-package.MyEntryListener
include-value: false
local: false
<hz:map name="somemap">
<hz:entry-listeners>
<hz:entry-listener include-value="true"
class-name="com.hazelcast.spring.DummyEntryListener"/>
<hz:entry-listener implementation="dummyEntryListener" local="true"/>
</hz:entry-listeners>
</hz:map>
Map Listener Attributes
As you see, there are attributes of the map listeners in the above
examples: include-value
and local
. The attribute include-value
is a boolean attribute that is optional, and if you set it to true
,
the map event contains the map value. Its default value is true
.
The attribute local
is also a boolean attribute that is optional, and
if you set it to true
, you can listen to the map on the local member.
Its default value is false
.
Listening for MultiMap Events
You can listen to entry-based events in the MultiMap using an entry listener. The following is an example entry listener implementation for MultiMap.
The entry listener for MultiMap supports only entryAdded , entryUpdated and mapCleared events.
|
public class ExampleEntryListener implements EntryListener<String, String> {
@Override
public void entryAdded(EntryEvent<String, String> event) {
System.out.println("Entry Added: " + event);
}
@Override
public void entryUpdated(EntryEvent<String, String> event) {
System.out.println( "Entry Updated: " + event );
}
@Override
public void mapCleared(MapEvent event) {
System.out.println( "Map Cleared: " + event );
}
@Override
public void entryRemoved(EntryEvent<String, String> event) {
}
@Override
public void entryEvicted(EntryEvent<String, String> event) {
}
@Override
public void entryExpired(EntryEvent<String, String> event) {
}
@Override
public void mapEvicted(MapEvent event) {
}
}
Registering MultiMap Listeners
After you create your listener class, you can configure your cluster to
include MultiMap listeners using the method addEntryListener
. Below
is the related portion from a code, showing how to register a map listener.
HazelcastInstance hz = Hazelcast.newHazelcastInstance();
MultiMap<String, String> map = hz.getMultiMap( "somemap" );
map.addEntryListener( new ExampleEntryListener(), true );
With the above approach, there is the possibility of missing events between the creation of the instance and registering the listener. To overcome this race condition, Hazelcast allows you to register listeners in the configuration. You can register listeners using declarative, programmatic, or Spring configuration, as shown below.
The following is an example programmatic configuration.
multiMapConfig.addEntryListenerConfig(
new EntryListenerConfig( "com.yourpackage.ExampleEntryListener",
false, false ) );
The following is an example of the equivalent declarative configuration.
<hazelcast>
...
<multimap name="somemap">
<value-collection-type>SET</value-collection-type>
<entry-listeners>
<entry-listener include-value="false" local="false">
com.yourpackage.ExampleEntryListener
</entry-listener>
</entry-listeners>
</multimap>
...
</hazelcast>
hazelcast:
multimap:
somemap:
value-collection: SET
entry-listeners:
- class-name: com.your-package.MyEntryListener
include-value: false
local: false
<hz:multimap name="somemap" value-collection-type="SET">
<hz:entry-listeners>
<hz:entry-listener include-value="false"
class-name="com.yourpackage.ExampleEntryListener"/>
<hz:entry-listener implementation="EntryListener" local="false"/>
</hz:entry-listeners>
</hz:multimap>
MultiMap Listener Attributes
As you see, there are attributes of the MultiMap listeners in the above
examples: include-value
and local
. The attribute include-value
is
a boolean attribute that is optional, and if you set it to true
, the
MultiMap event contains the map value. Its default value is true
.
The attribute local
is also a boolean attribute that is optional, and
if you set it to true
, you can listen to the MultiMap on the local member.
Its default value is false
.
Listening for Item Events
The Item Listener is used by the Hazelcast IQueue
, ISet
and IList
interfaces.
To write an Item Listener class, you implement the ItemListener
interface and its methods itemAdded
and itemRemoved
. These methods
are invoked when an item is added or removed.
The following is an example Item Listener class for an ISet
structure.
public class ExampleItemListener implements ItemListener<Price> {
@Override
public void itemAdded(ItemEvent<Price> event) {
System.out.println( "Item added: " + event );
}
@Override
public void itemRemoved(ItemEvent<Price> event) {
System.out.println( "Item removed: " + event );
}
}
You can use ICollection when creating any of the collection
(queue, set and list) data structures, as shown above. You can also
use IQueue , ISet or IList instead of ICollection .
|
Registering Item Listeners
After you create your class, you can configure your cluster to include
item listeners. Below is an example using the method addItemListener
for ISet
(it applies also to IQueue
and IList
). You can also see
this portion in the above class creation.
HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance();
ICollection<Price> set = hazelcastInstance.getSet( "default" );
// or ISet<Prices> set = hazelcastInstance.getSet( "default" );
set.addItemListener( new ExampleItemListener(), true );
With the above approach, there is the possibility of missing events between the creation of the instance and registering the listener. To overcome this race condition, Hazelcast allows you to register listeners in the configuration. You can register listeners using declarative, programmatic, or Spring configuration, as shown below.
The following is an example programmatic configuration.
setConfig.addItemListenerConfig(
new ItemListenerConfig( "com.yourpackage.ExampleItemListener", true ) );
The following is an example of the equivalent declarative configuration.
<hazelcast>
...
<set>
<item-listeners>
<item-listener include-value="true">
com.yourpackage.ExampleItemListener
</item-listener>
</item-listeners>
</set>
...
</hazelcast>
hazelcast:
set:
default:
item-listeners:
- class-name: com.yourpackage.ExampleItemListener
include-value: true
<hz:set name="default" >
<hz:item-listeners>
<hz:item-listener include-value="true"
class-name="com.yourpackage.ExampleItemListener"/>
</hz:item-listeners>
</hz:set>
Listening for Topic Messages
The Message Listener is used by the ITopic
interface. It notifies
when a message is received for the registered topic.
To write a Message Listener class, you implement the MessageListener
interface and its method onMessage
, which is invoked
when a message is received for the registered topic.
The following is an example Message Listener class.
public class ExampleMessageListener implements MessageListener<MyEvent> {
public void onMessage( Message<MyEvent> message ) {
MyEvent myEvent = message.getMessageObject();
System.out.println( "Message received = " + myEvent.toString() );
}
}
Reliable Topic Messages
ReliableMessageListener is a MessageListener to better integrate with the reliable topic.
If a standard MessageListener
is registered on a reliable topic, it acts only as an asynchronous event handler for messages; however, a ReliableMessageListener
allows you to control the behavior of the listener.
If a ReliableMessageListener
is registered on a normal ITopic
, only the methods inherited from MessageListener
are called.
For example, a Reliable Message Listener class can be defined as follows:
public class ExampleReliableMessageListener implements ReliableMessageListener<Long> {
private long lastSequence;
public void onMessage(Message<Long> m) {
System.out.println("Received: " + m.getMessageObject());
}
@Override
public long retrieveInitialSequence() {
// The initial sequence to start reading from.
return 1;
}
@Override
public void storeSequence(long l) {
// The sequence to store somewhere so that it can be retrieved upon restart.
lastSequence = l;
System.out.println("Stored sequence: " + l);
}
@Override
public boolean isLossTolerant() {
System.out.println("isLossTolerant called");
// If true, the listener will not be removed upon an exception.
return false;
}
@Override
public boolean isTerminal(Throwable throwable) {
System.out.println("isTerminal called");
return false;
}
@Override
public void onCancel() {
System.out.println("onCancel called. The listener is being removed.");
}
}
Durable Subscription
You can use the ReliableMessageListener
to control which message to start processing from when the listener is registered. This supports the creation of a durable subscription by storing the sequence of the last message and then re-starting from the specified sequenceId
.
Exception Handling
The ReliableMessageListener
also copes with exceptions using the isTerminal(Throwable)
method. This method allows you to control which exceptions can terminate and cancel the listener. If a MessageListener
is used, it doesn’t terminate following an exception and continues to run. In some situations, such as cluster being stopped, it is better to cancel the listener following the exception.
Global Order
The ReliableMessageListener
always gets all events in order (global order). It does not get duplicates, and gaps (loss of messages) only occur when it is too slow. For more information on dealing with message loss, refer to the isLossTolerant()
method in the Java API documentation.
Delivery Guarantees
The ReliableMessageListener
controls the item to continue from following a restart, providing an at-least-once or at-most-once delivery guarantee. The storeSequence(long sequence)
is always called before a message is processed; so it can be persisted on non-volatile storage. When the retrieveInitialSequence()
returns the stored sequence, an at-least-once delivery is implemented as the same item is processed again. To implement an at-most-once delivery guarantee, add 1 to the stored sequence when the retrieveInitialSequence()
is called.
Loss Tolerance
If this ReliableMessageListener
can deal with message loss, the boolean isLosstolerant()
method returns true
. Even though the reliable topic promises to be reliable, a MessageListener
can be too slow and eventually this causes the message to become unavailable.
If the ReliableMessageListener
is not loss tolerant and the topic detects that there are missing messages, the ReliableMessageListener
terminates.
onCancel Callback
This method is called by Hazelcast when the ReliableMessageListener
is canceled. This can happen when the listener is unregistered, or when it has been canceled due to an exception or shutdown.
Registering Message Listeners
After you create your class, you can configure your cluster to include
message listeners. Below is an example using the method addMessageListener
.
HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance();
ITopic topic = hazelcastInstance.getTopic( "default" );
topic.addMessageListener( new ExampleMessageListener() );
With the above approach, there is the possibility of missing messaging events between the creation of the instance and registering the listener. To overcome this race condition, Hazelcast allows you to register this listener in the configuration. You can register it using declarative, programmatic, or Spring configuration, as shown below.
The following is an example programmatic configuration.
topicConfig.addMessageListenerConfig(
new ListenerConfig( "com.yourpackage.ExampleMessageListener" ) );
The following is an example of the equivalent declarative configuration.
<hazelcast>
...
<topic name="default">
<message-listeners>
<message-listener>
com.yourpackage.ExampleMessageListener
</message-listener>
</message-listeners>
</topic>
...
</hazelcast>
hazelcast:
topic:
default:
message-listeners:
- com.yourpackage.ExampleMessageListener
<hz:topic name="default">
<hz:message-listeners>
<hz:message-listener
class-name="com.yourpackage.ExampleMessageListener"/>
</hz:message-listeners>
</hz:topic>