7.7. Remote Event Listeners (Hot Rod)
7.7. Remote Event Listeners (Hot Rod) Event listeners allow Red Hat JBoss Data Grid Hot Rod servers to be able to notify remote clients of events such as CacheEntryCreated , CacheEntryModified , CacheEntryExpired and CacheEntryRemoved . Clients can choose whether or not to listen to these events to avoid flooding connected clients. This assumes that clients maintain persistent connections to the servers. Client listeners for remote events can be added similarly to clustered listeners in library mode. The following example demonstrates a remote client listener that prints out each event it receives. Example 7.7. Event Print Listener ClientCacheEntryCreatedEvent and ClientCacheEntryModifiedEvent instances provide information on the key and version of the entry. This version can be used to invoke conditional operations on the server, such a replaceWithVersion or removeWithVersion . ClientCacheEntryExpiredEvent events are sent when either a get() is called on an expired entry, or when the expiration reaper detects that an entry has expired. Once the entry has expired the cache will nullify the entry, and adjust its size appropriately; however, the event will only be generated in the two scenarios listed. ClientCacheEntryRemovedEvent events are only sent when the remove operation succeeds. If a remove operation is invoked and no entry is found or there are no entries to remove, no event is generated. If users require remove events regardless of whether or not they are successful, a customized event logic can be created. All client cache entry created, modified, and removed events provide a boolean isCommandRetried() method that will return true if the write command that caused it has to be retried due to a topology change. This indicates that the event has been duplicated or that another event was dropped and replaced, such as where a Modified event replaced a Created event. Important If the expected workload favors writes over reads it will be necessary to filter the events sent to prevent a large amount of excessive traffic being generated which may cause issues on either the client or the network. For more details on filtering events refer to Section 7.7.3, "Filtering Remote Events" . Important Remote event listeners are available for the Hot Rod Java client only. Report a bug 7.7.1. Adding and Removing Event Listeners Registering and Event Listener with the Server The following example registers the Event Print Listener with the server. See Example 7.7, "Event Print Listener" . Example 7.8. Adding an Event Listener Removing a Client Event Listener A client event listener can be removed as follows Example 7.9. Removing an Event Listener Report a bug 7.7.2. Remote Event Client Listener Example The following procedure demonstrates the steps required to configure a remote client listener to interact with the remote cache via Hot Rod. Procedure 7.2. Configuring Remote Event Listeners Download the Red Hat JBoss Data Grid Server distribution from the Red Hat Customer Portal The latest Red Hat JBoss Data Grid distribution includes the Hot Rod server with which the client will communicate. Start the server Start the JBoss Data Grid server by using the following command from the root of the server. Write an application to interact with the Hot Rod server Maven users Create an application with the following dependency, changing the version to 6.3.0-Final-redhat-1 or better. Non-Maven users, adjust according to your chosen build tool or download the distribution containing all JBoss Data Grid jars. Write the client application The following demonstrates a simple remote event listener that logs all events received. Use the remote event listener to execute operations against the remote cache The following example demonstrates a simple main java class, which adds the remote event listener and executes some operations against the remote cache. Result Once executed, the console output should appear similar to the following: The output indicates that by default, events come with the key and the internal data version associated with current value. The actual value is not sent back to the client for performance reasons. Receiving remote events has a performance impact, which is increased with cache size, as more operations are executed. To avoid inundating Hot Rod clients, filter remote events on the server side, or customize the event contents. Report a bug 7.7.3. Filtering Remote Events To prevent clients being inundated with events, Red Hat JBoss Data Grid Hot Rod remote events can be filtered by providing key/value filter factories that create instances that filter which events are sent to clients, and how these filters can act on client provided information. Sending events to remote clients has a performance cost, which increases with the number of clients with registered remote listeners. The performance impact also increases with the number of modifications that are executed against the cache. The performance cost can be reduced by filtering the events being sent on the server side. Custom code can be used to exclude certain events from being broadcast to the remote clients to improve performance. Filtering can be based on either key or value information, or based on cache entry metadata. To enable filtering, a cache event filter factory that produces filter instances must be created. The following is a sample implementation that filters key "2" out of the events sent to clients. Example 7.10. KeyValueFilter In order to register a listener with this key value filter factory, the factory must be given a unique name, and the Hot Rod server must be plugged with the name and the cache event filter factory instance. Report a bug 7.7.3.1. Custom Filters for Remote Events Custom filters can improve performance by excluding certain event information from being broadcast to the remote clients. To plug the JBoss Data Grid Server with a custom filter use the following procedure: Procedure 7.3. Using a Custom Filter Create a JAR file with the filter implementation within it. Each factory must have a name assigned to it via the org.infinispan.filter.NamedFactory annotation. The example uses a KeyValueFilterFactory . Create a META-INF/services/org.infinispan.notifications.cachelistener.filter. CacheEventFilterFactory file within the JAR file, and within it write the fully qualified class name of the filter class implementation. Deploy the JAR file in the JBoss Data Grid Server by performing any of the following options: Procedure 7.4. Option 1: Deploy the JAR through the deployment scanner. Copy the JAR to the USDJDG_HOME/standalone/deployments/ directory. The deployment scanner actively monitors this directory and will deploy the newly placed file. Procedure 7.5. Option 2: Deploy the JAR through the CLI Connect to the desired instance with the CLI: Once connected execute the deploy command: Procedure 7.6. Option 3: Deploy the JAR as a custom module Connect to the JDG server by running the below command: The jar containing the Custom Filter must be defined as a module for the Server; to add this substitute the desired name of the module and the .jar name in the below command, adding additional dependencies as necessary for the Custom Filter: In a different window add the newly added module as a dependency to the org.infinispan module by editing USDJDG_HOME/modules/system/layers/base/org/infinispan/main/module.xml . In this file add the following entry: Restart the JDG server. Once the server is plugged with the filter, add a remote client listener that will use the filter. The following example extends the EventLogListener implementation provided in Remote Event Client Listener Example (See Section 7.7.2, "Remote Event Client Listener Example" ), and overrides the @ClientListener annotation to indicate the filter factory to use with the listener. Example 7.11. Add Filter Factory to the Listener The listener can now be added via the RemoteCacheAPI. The following example demonstrates this, and executes some operations against the remote cache. Example 7.12. Register the Listener with the Server The system output shows that the client receives events for all keys except those that have been filtered. Result The following demonstrates the resulting system output from the provided example. Important Filter instances must be marshallable when they are deployed in a cluster in order for filtering to occur where the event is generated, even if the event is generated in a different node to where the listener is registered. To make them marshallable, either make them extend Serializable, Externalizable, or provide a custom Externalizer. Report a bug 7.7.3.2. Enhanced Filter Factories When adding client listeners, users can provide parameters to the filter factory in order to generate different filter instances with different behaviors from a single filter factory based on client-side information. The following configuration demonstrates how to enhance the filter factory so that it can filter dynamically based on the key provided when adding the listener, rather than filtering on a statically given key. Example 7.13. Configuring an Enhanced Filter Factory The filter can now filter by "3" instead of "2": Example 7.14. Running an Enhanced Filter Factory Result The provided example results in the following output: The amount of information sent to clients can be further reduced or increased by customizing remote events. Report a bug 7.7.4. Customizing Remote Events In Red Hat JBoss Data Grid, Hot Rod remote events can be customized to contain the information required to be sent to a client. By default, events contain only a basic set of information, such as a key and type of event, in order to avoid overloading the client, and to reduce the cost of sending them. The information included in these events can be customized to contain more information, such as values, or contain even less information. Customization is done via CacheEventConverter instances, which are created by implementing a CacheEventConverterFactory class. Each factory must have a name associated to it via the @NamedFactory annotation. To plug the JBoss Data Grid Server with an event converter use the following procedure: Procedure 7.7. Using a Converter Create a JAR file with the converter implementation within it. Each factory must have a name assigned to it via the org.infinispan.filter.NamedFactory annotation. Create a META-INF/services/org.infinispan.notifications.cachelistener.filter.CacheEventConverterFactory file within the JAR file and within it, write the fully qualified class name of the converter class implementation. Deploy the JAR file in the JBoss Data Grid Server by performing any of the following options: Procedure 7.8. Option 1: Deploy the JAR through the deployment scanner. Copy the JAR to the USDJDG_HOME/standalone/deployments/ directory. The deployment scanner actively monitors this directory and will deploy the newly placed file. Procedure 7.9. Option 2: Deploy the JAR through the CLI Connect to the desired instance with the CLI: Once connected execute the deploy command: Procedure 7.10. Option 3: Deploy the JAR as a custom module Connect to the JDG server by running the below command: The jar containing the Custom Converter must be defined as a module for the Server; to add this substitute the desired name of the module and the .jar name in the below command, adding additional dependencies as necessary for the Custom Converter: In a different window add the newly added module as a dependency to the org.infinispan module by editing USDJDG_HOME/modules/system/layers/base/org/infinispan/main/module.xml . In this file add the following entry: Restart the JDG server. Converters can also act on client provided information, allowing converter instances to customize events based on the information provided when the listener was added. The API allows converter parameters to be passed in when the listener is added. Report a bug 7.7.4.1. Adding a Converter When a listener is added, the name of a converter factory can be provided to use with the listener. When the listener is added, the server looks up the factory and invokes the getConverter method to get a org.infinispan.filter.Converter class instance to customize events server side. The following example demonstrates sending custom events containing value information to remote clients for a cache of Integers and Strings. The converter generates a new custom event, which includes the value as well as the key in the event. The custom event has a bigger event payload compared with default events, however if combined with filtering, it can reduce bandwidth cost. Example 7.15. Sending Custom Events Report a bug 7.7.4.2. Lightweight Events Other converter implementations are able to send back events that contain no key or event type information, resulting in extremely lightweight events at the expense of having rich information provided by the event. In order to plug the server with this converter, deploy the converter factory and associated converter class within a JAR file including a service definition inside the META-INF/services/org.infinispan.notifications.cachelistener.filter.CacheEventConverterFactory file as follows: The client listener must then be linked with the converter factory by adding the factory name to the @ClientListener annotation. Report a bug 7.7.4.3. Dynamic Converter Instances Dynamic converter instances convert based on parameters provided when the listener is registered. Converters use the parameters received by the converter factories to enable this option. For example: Example 7.16. Dynamic Converter The dynamic parameters required to do the conversion are provided when the listener is registered: Report a bug 7.7.4.4. Adding a Remote Client Listener for Custom Events Implementing a listener for custom events is slightly different to other remote events, as they involve non-default events. The same annotations are used as in other remote client listener implementations, but the callbacks receive instances of ClientCacheEntryCustomEvent<T> , where T is the type of custom event we are sending from the server. For example: Example 7.17. Custom Event Listener Implementation To use the remote event listener to execute operations against the remote cache, write a simple main Java class, which adds the remote event listener and executes some operations against the remote cache. For example: Example 7.18. Execute Operations against the Remote Cache Result Once executed, the console output should appear similar to the following: Important Converter instances must be marshallable when they are deployed in a cluster in order for conversion to occur where the event is generated, even if the event is generated in a different node to where the listener is registered. To make them marshallable, either make them extend Serializable, Externalizable, or provide a custom Externalizer for them. Both client and server need to be aware of any custom event type and be able to marshall it in order to facilitate both server and client writing against type safe APIs. On the client side, this is done by an optional marshaller configurable via the RemoteCacheManager. On the server side, this is done by a marshaller added to the Hot Rod server configuration. Report a bug 7.7.5. Event Marshalling When filtering or customizing events, the KeyValueFilter and Converter instances must be marshallable. As the client listener is installed in a cluster, the filter and/or converter instances are sent to other nodes in the cluster in order for filtering and conversion to occur where the event originates, improving efficiency. These classes can be made marshallable by having them extend Serializable or by providing and registering a custom Externalizer. To deploy a Marshaller instance server-side, use a similar method to that used for filtering and customized events. Procedure 7.11. Deploying a Marshaller Create a JAR file with the converter implementation within it. Each factory must have a name assigned to it via the org.infinispan.filter.NamedFactory annotation. Create a META-INF/services/org.infinispan.commons.marshall.Marshaller file within the JAR file and within it, write the fully qualified class name of the marshaller class implementation Deploy the JAR file in the JBoss Data Grid Server by performing any of the following options: Procedure 7.12. Option 1: Deploy the JAR through the deployment scanner. Copy the JAR to the USDJDG_HOME/standalone/deployments/ directory. The deployment scanner actively monitors this directory and will deploy the newly placed file. Procedure 7.13. Option 2: Deploy the JAR through the CLI Connect to the desired instance with the CLI: Once connected execute the deploy command: Procedure 7.14. Option 3: Deploy the JAR as a custom module Connect to the JDG server by running the below command: The jar containing the Custom Marshaller must be defined as a module for the Server; to add this substitute the desired name of the module and the .jar name in the below command, adding additional dependencies as necessary for the Custom Marshaller: In a different window add the newly added module as a dependency to the org.infinispan module by editing USDJDG_HOME/modules/system/layers/base/org/infinispan/main/module.xml . In this file add the following entry: Restart the JDG server. The Marshaller can be deployed either in a separate jar, or in the same jar as the CacheEventConverter, and/or CacheEventFilter instances. Note Only the deployment of a single Marshaller instance is supported. If multiple marshaller instances are deployed, warning messages will be displayed as a reminder indicating which marshaller instance will be used. Report a bug 7.7.6. Remote Event Clustering and Failover When a client adds a remote listener, it is installed in a single node in the cluster, which is in charge of sending events back to the client for all affected operations that occur cluster-wide. In a clustered environment, when the node containing the listener goes down, the Hot Rod client implementation transparently fails over the client listener registration to a different node. This may result in a gap in event consumption, which can be solved using one of the following solutions. State Delivery The @ClientListener annotation has an optional includeCurrentState parameter, which when enabled, has the server send CacheEntryCreatedEvent event instances for all existing cache entries to the client. As this behavior is driven by the client it detects when the node where the listener is registered goes offline and automatically registers the listener on another node in the cluster. By enabling includeCurrentState clients may recompute their state or computation in the event the Hot Rod client transparently fails over registered listeners. The performance of the includeCurrentState parameter is impacted by the cache size, and therefore it is disabled by default. @ClientCacheFailover Rather than relying on receiving state, users can define a method with the @ClientCacheFailover annotation, which receives ClientCacheFailoverEvent parameter inside the client listener implementation. If the node where a Hot Rod client has registered a client listener fails, the Hot Rod client detects it transparently, and fails over all listeners registered in the node that failed to another node. During this failover, the client may miss some events. To avoid this, the includeCurrentState parameter can be set to true. With this enabled a client is able to clear its data, receive all of the CacheEntryCreatedEvent instances, and cache these events with all keys. Alternatively, Hot Rod clients can be made aware of failover events by adding a callback handler. This callback method is an efficient solution to handling cluster topology changes affecting client listeners, and allows the client listener to determine how to behave on a failover. Near Caching takes this approach and clears the near cache upon receiving a ClientCacheFailoverEvent . Example 7.19. @ClientCacheFailover Note The ClientCacheFailoverEvent is only thrown when the node that has the client listener installed fails. Report a bug
import org.infinispan.client.hotrod.annotation.*; import org.infinispan.client.hotrod.event.*; @ClientListener public class EventLogListener { @ClientCacheEntryCreated public void handleCreatedEvent(ClientCacheEntryCreatedEvent e) { System.out.println(e); } @ClientCacheEntryModified public void handleModifiedEvent(ClientCacheEntryModifiedEvent e) { System.out.println(e); } @ClientCacheEntryExpired public void handleExpiredEvent(ClientCacheEntryExpiredEvent e) { System.out.println(e); } @ClientCacheEntryRemoved public void handleRemovedEvent(ClientCacheEntryRemovedEvent e) { System.out.println(e); } } RemoteCache<Integer, String> cache = rcm.getCache(); cache.addClientListener(new EventLogListener()); EventLogListener listener = cache.removeClientListener(listener); ./bin/standalone.sh <properties> <infinispan.version>6.3.0-Final-redhat-1</infinispan.version> </properties> [...] <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-remote</artifactId> <version>USD{infinispan.version}</version> </dependency> import org.infinispan.client.hotrod.annotation.*; import org.infinispan.client.hotrod.event.*; @ClientListener public class EventLogListener { @ClientCacheEntryCreated @ClientCacheEntryModified @ClientCacheEntryRemoved public void handleRemoteEvent(ClientEvent event) { System.out.println(event); } } import org.infinispan.client.hotrod.*; RemoteCacheManager rcm = new RemoteCacheManager(); RemoteCache<Integer, String> cache = rcm.getCache(); EventLogListener listener = new EventLogListener(); try { cache.addClientListener(listener); cache.put(1, "one"); cache.put(1, "new-one"); cache.remove(1); } finally { cache.removeClientListener(listener); } ClientCacheEntryCreatedEvent(key=1,dataVersion=1) ClientCacheEntryModifiedEvent(key=1,dataVersion=2) ClientCacheEntryRemovedEvent(key=1) package sample; import java.io.Serializable; import org.infinispan.notifications.cachelistener.filter.*; import org.infinispan.metadata.*; @NamedFactory(name = "basic-filter-factory") public class BasicKeyValueFilterFactory implements CacheEventFilterFactory { @Override public CacheEventFilter<Integer, String> getKeyValueFilter(final Object[] params) { return new BasicKeyValueFilter(); } static class BasicKeyValueFilter implements CacheEventFilter<Integer, String>, Serializable { @Override public boolean accept(Integer key, String oldValue, Metadata oldMetadata, String newValue, Metadata newMetadata, EventType eventType) { return !"2".equals(key); } } } [USDJDG_HOME] USD bin/cli.sh --connect=USDIP:USDPORT deploy /path/to/artifact.jar [USDJDG_HOME] USD bin/cli.sh --connect=USDIP:USDPORT module add --name=USDMODULE-NAME --resources=USDJAR-NAME.jar --dependencies=org.infinispan <dependencies> [...] <module name="USDMODULE-NAME"> </dependencies> @org.infinispan.client.hotrod.annotation.ClientListener(filterFactoryName = "basic-filter-factory") public class BasicFilteredEventLogListener extends EventLogListener {} import org.infinispan.client.hotrod.*; RemoteCacheManager rcm = new RemoteCacheManager(); RemoteCache<Integer, String> cache = rcm.getCache(); BasicFilteredEventLogListener listener = new BasicFilteredEventLogListener(); try { cache.addClientListener(listener); cache.putIfAbsent(1, "one"); cache.replace(1, "new-one"); cache.putIfAbsent(2, "two"); cache.replace(2, "new-two"); cache.putIfAbsent(3, "three"); cache.replace(3, "new-three"); cache.remove(1); cache.remove(2); cache.remove(3); } finally { cache.removeClientListener(listener); } ClientCacheEntryCreatedEvent(key=1,dataVersion=1) ClientCacheEntryModifiedEvent(key=1,dataVersion=2) ClientCacheEntryCreatedEvent(key=3,dataVersion=5) ClientCacheEntryModifiedEvent(key=3,dataVersion=6) ClientCacheEntryRemovedEvent(key=1) ClientCacheEntryRemovedEvent(key=3) package sample; import java.io.Serializable; import org.infinispan.notifications.cachelistener.filter.*; import org.infinispan.metadata.*; @NamedFactory(name = "basic-filter-factory") public class BasicKeyValueFilterFactory implements CacheEventFilterFactory { @Override public CacheEventFilter<Integer, String> getKeyValueFilter(final Object[] params) { return new BasicKeyValueFilter(params); } static class BasicKeyValueFilter implements CacheEventFilter<Integer, String>, Serializable { private final Object[] params; public BasicKeyValueFilter(Object[] params) { this.params = params; } @Override public boolean accept(Integer key, String oldValue, Metadata oldMetadata, String newValue, Metadata newMetadata, EventType eventType) { return !params[0].equals(key); } } } import org.infinispan.client.hotrod.*; RemoteCacheManager rcm = new RemoteCacheManager(); RemoteCache<Integer, String> cache = rcm.getCache(); BasicFilteredEventLogListener listener = new BasicFilteredEventLogListener(); try { cache.addClientListener(listener, new Object[]{3}, null); // <- Filter parameter passed cache.putIfAbsent(1, "one"); cache.replace(1, "new-one"); cache.putIfAbsent(2, "two"); cache.replace(2, "new-two"); cache.putIfAbsent(3, "three"); cache.replace(3, "new-three"); cache.remove(1); cache.remove(2); cache.remove(3); } finally { cache.removeClientListener(listener); } ClientCacheEntryCreatedEvent(key=1,dataVersion=1) ClientCacheEntryModifiedEvent(key=1,dataVersion=2) ClientCacheEntryCreatedEvent(key=2,dataVersion=3) ClientCacheEntryModifiedEvent(key=2,dataVersion=4) ClientCacheEntryRemovedEvent(key=1) ClientCacheEntryRemovedEvent(key=2) [USDJDG_HOME] USD bin/cli.sh --connect=USDIP:USDPORT deploy /path/to/artifact.jar [USDJDG_HOME] USD bin/cli.sh --connect=USDIP:USDPORT module add --name=USDMODULE-NAME --resources=USDJAR-NAME.jar --dependencies=org.infinispan <dependencies> [...] <module name="USDMODULE-NAME"> </dependencies> import org.infinispan.notifications.cachelistener.filter.*; @NamedFactory(name = "value-added-converter-factory") class ValueAddedConverterFactory implements CacheEventConverterFactory { // The following types correspond to the Key, Value, and the returned Event, respectively. public CacheEventConverter<Integer, String, ValueAddedEvent> getConverter(final Object[] params) { return new ValueAddedConverter(); } static class ValueAddedConverter implements CacheEventConverter<Integer, String, ValueAddedEvent> { public ValueAddedEvent convert(Integer key, String oldValue, Metadata oldMetadata, String newValue, Metadata newMetadata, EventType eventType) { return new ValueAddedEvent(key, value); } } } // Must be Serializable or Externalizable. class ValueAddedEvent implements Serializable { final Integer key; final String value; ValueAddedEvent(Integer key, String value) { this.key = key; this.value = value; } } sample.ValueAddedConverterFactor @ClientListener(converterFactoryName = "value-added-converter-factory") public class CustomEventLogListener { ... } import org.infinispan.notifications.cachelistener.filter.CacheEventConverterFactory; import org.infinispan.notifications.cachelistener.filter.CacheEventConverter; class DynamicCacheEventConverterFactory implements CacheEventConverterFactory { // The following types correspond to the Key, Value, and the returned Event, respectively. public CacheEventConverter<Integer, String, CustomEvent> getConverter(final Object[] params) { return new DynamicCacheEventConverter(params); } } // Serializable, Externalizable or marshallable with Infinispan Externalizers needed when running in a cluster class DynamicCacheEventConverter implements CacheEventConverter<Integer, String, CustomEvent>, Serializable { final Object[] params; DynamicCacheEventConverter(Object[] params) { this.params = params; } public CustomEvent convert(Integer key, String oldValue, Metadata metadata, String newValue, Metadata prevMetadata, EventType eventType) { // If the key matches a key given via parameter, only send the key information if (params[0].equals(key)) return new ValueAddedEvent(key, null); return new ValueAddedEvent(key, newValue); } } RemoteCache<Integer, String> cache = rcm.getCache(); cache.addClientListener(new EventLogListener(), null, new Object[]{1}); import org.infinispan.client.hotrod.annotation.*; import org.infinispan.client.hotrod.event.*; @ClientListener(converterFactoryName = "value-added-converter-factory") public class CustomEventLogListener { @ClientCacheEntryCreated @ClientCacheEntryModified @ClientCacheEntryRemoved public void handleRemoteEvent(ClientCacheEntryCustomEvent<ValueAddedEvent> event) { System.out.println(event); } } import org.infinispan.client.hotrod.*; RemoteCacheManager rcm = new RemoteCacheManager(); RemoteCache<Integer, String> cache = rcm.getCache(); CustomEventLogListener listener = new CustomEventLogListener(); try { cache.addClientListener(listener); cache.put(1, "one"); cache.put(1, "new-one"); cache.remove(1); } finally { cache.removeClientListener(listener); } ClientCacheEntryCustomEvent(eventData=ValueAddedEvent{key=1, value='one'}, eventType=CLIENT_CACHE_ENTRY_CREATED) ClientCacheEntryCustomEvent(eventData=ValueAddedEvent{key=1, value='new-one'}, eventType=CLIENT_CACHE_ENTRY_MODIFIED) ClientCacheEntryCustomEvent(eventData=ValueAddedEvent{key=1, value='null'}, eventType=CLIENT_CACHE_ENTRY_REMOVED [USDJDG_HOME] USD bin/cli.sh --connect=USDIP:USDPORT deploy /path/to/artifact.jar [USDJDG_HOME] USD bin/cli.sh --connect=USDIP:USDPORT module add --name=USDMODULE-NAME --resources=USDJAR-NAME.jar --dependencies=org.infinispan <dependencies> [...] <module name="USDMODULE-NAME"> </dependencies> import org.infinispan.client.hotrod.annotation.*; import org.infinispan.client.hotrod.event.*; @ClientListener public class EventLogListener { // @ClientCacheFailover public void handleFailover(ClientCacheFailoverEvent e) { // Deal with client failover, e.g. clear a near cache. } }