Fusion Middleware Documentation
Advanced Search


Developing Applications with Oracle Coherence
Close Window

Table of Contents

Show All | Collapse

27 Using Live Events

This chapter describes live events and provides instructions for being notified of events using event interceptors. Applications use live events to react to cluster operations with application logic.

This chapter includes the following sections:

27.1 Overview of Live Events

Coherence provides an event programming model that allows extensibility within a cluster when performing operations against a data grid. The model uses events to represent observable occurrences of cluster operations. The events that are currently supported include:

  • Partitioned Cache Events – A set of events that represent the operations being performed against a set of entries in a cache. Partitioned cache events include both entry events and entry processor events. Entry events are related to inserting, removing, and updating entries in a cache. Entry processor events are related to the execution of entry processors.

  • Partitioned Service Events – A set of events that represent the operations being performed by a partitioned service. Partitioned service events include both partition transfer events and partition transaction events. Partition transfer events are related to the movement of partitions among cluster members. Partition transaction events are related to changes that may span multiple caches and are performed within the context of a single request.

  • Lifecycle Events – A set of events that represent the activation and disposal of a ConfigurableCacheFactory instance.

Applications create and register event interceptors to consume events. Event interceptors handle the events and implement any custom logic as required. Events have different rules which govern whether or not mutable actions may be performed upon receiving the event.

27.2 Understanding Live Event Types

Event types represent observable occurrences of cluster operations. Applications handle the events using event interceptors and decide what action to take based on the event type. This section describes each of the supported event types and is organized according to the functional areas in which the events are raised.

The following topics are included in this section:

27.2.1 Understanding Partitioned Cache Events

Partitioned cache events represent operations that are performed against a set of entries in a cache. Partitioned cache events include entry events (EntryEvent) and entry processor events (EntryProcessorEvent). These events are defined within the com.tangosol.net.events.partition.cache package.

27.2.1.1 Entry Events

Entry events represent operations for inserting, removing, and updating entries in a cache. Precommit entry events (INSERTING, REMOVING, and UPDATING) are raised before the operation is performed to allow modification to an entry. The following holds true when modifying entries:

  • Event interceptors that are registered for precommit entry events are synchronously called.

  • A lock is held for each entry during the processing of the event to prevent concurrent updates.

  • Throwing an exception prevents the operation from being committed.

Postcommit entry events (INSERTED, REMOVED, and UPDATED) are raised after an operation is performed and in the same order as the events occurred. Postcommit events indicate that an entry is no longer able to be modified. Event interceptors for postcommit entry events are asynchronously processed.

Table 27-1 lists the entry event types.

Table 27-1 Entry Events

Event Types Description

INSERTING

Indicates that an entry (or multiple entries) is going to be inserted in the cache.

INSERTED

Indicates that an entry (or multiple entries) has been inserted in the cache.

REMOVING

Indicates that an entry (or multiple entries) is going to be removed from the cache.

REMOVED

Indicates that an entry (or multiple entries) has been removed from the cache.

UPDATING

Indicates that an entry (or multiple entries) is going to be updated in the cache.

UPDATED

Indicates that an entry (or multiple entries) has been updated in the cache.


27.2.1.2 Entry Processor Events

Entry processor events represent the execution of an entry processor on a set of binary entries. Precommit entry processor events (EXECUTING) are raised before an entry processor is executed to allow modification to the entry processor instance. The following holds true when modifying an entry processor:

  • Event interceptors that are registered for precommit entry processor events are synchronously called.

  • Entry processors can be shared across threads; therefore, ensure thread safety when modifying an entry processor.

  • A lock is held for each entry during the processing of the event to prevent concurrent updates.

  • Throwing an exception prevents the entry processor from being executed.

Postcommit entry processor events (EXECUTED) are raised after an entry processor is executed and in the same order that the events occurred. Postcommit events indicate that an entry is no longer able to be modified. Event interceptors for postcommit entry processor events are asynchronously processed.

Table 27-2 lists the entry processor event types.

Table 27-2 Entry Processor Events

Event Types Description

EXECUTING

Indicates that an entry processor is going to be executed on a set of entries.

EXECUTED

Indicates that an entry processor has been executed on a set of entries.


27.2.2 Understanding Partitioned Service Events

Partitioned service events represent operations being performed by a partitioned service. Partitioned service events include transfer events (TransferEvent) and transaction events (TransactionEvent). These events are defined within the com.tangosol.net.events.partition package.

27.2.2.1 Transfer Events

Partitioned service transfer events represent partition transfers between storage enabled members. The event includes the service name for which the transfer is taking place, the partition ID, the cluster members involved in the transfer and a map of cache names to entries. The entries cannot be modified.

Note:

Transfer events are raised while holding a lock on the partition being transferred that blocks any operations for the partition.

Table 27-3 lists the transition event types.

Table 27-3 Transition Events

Event Types Description

DEPARTING

Indicates that a set of entries are being transferred from the current member.

ARRIVED

Indicates that a set of entries has been transferred to or restored by the current member.


27.2.2.2 Transaction Events

Partitioned service transaction events represent changes to binary entries (possibly from multiple caches) that are made in the context of a single service request. Precommit transaction events (COMMITTING) are raised before any operations are performed to allow modification to the entries. The following holds true when modifying entries:

  • A lock is held for each entry during the processing of the event to prevent concurrent updates.

  • Throwing an exception prevents the operation from being committed.

Postcommit transaction events (COMMITTED) are raised after an operation is performed. Postcommit events indicate that the entries are no longer able to be modified.

Table 27-4 lists the transaction event types.

Table 27-4 Transaction Events

Event Types Description

COMMITTING

Indicates that entries are going to be inserted in their respective cache.

COMMITTED

Indicates that entries have been inserted in their respective cache.


27.2.3 Understanding Lifecycle Events

Lifecycle events (LifecycleEvent) represent actions that occur on a ConfigurableCacheFactory instance. These events are defined within the com.tangosol.net.events.application package.

An ACTIVATED event is raised after all services that are associated with a cache factory are started. The services are defined in the cache configuration file and must be configured to autostart. A DISPOSING event is raised before all services are shut down and any resources are reclaimed. Event interceptors that handle a DISPOSING event are notified before the services are shutdown. A ConfigurableCacheFactory instance can only be activated and disposed of once.

Note:

Lifecycle events are dispatched to event interceptors by the same thread calling the lifecycle methods on the ConfigurableCacheFactory implementation. This thread may be synchronized. Event interceptors must ensure that any spawned threads do not synchronize on the same ConfigurableCacheFactory object.

Table 27-5 lists the lifecycle event types.

Table 27-5 Lifecycle Events

Event Types Description

ACTIVATED

Indicates that a ConfigurableCacheFactory instance is active.

DISPOSING

Indicates that a ConfigurableCacheFactory instance is going to be disposed.


27.3 Handling Live Events

Applications handle live events using event interceptors. The interceptors explicitly define which events to receive and what action, if any, to take. Any number of event interceptors can be created and registered for a specific cache or for all caches managed by a specific partitioned service. Multiple interceptors that are registered for the same event type are automatically chained together and executed in the context of a single event.

This section includes the following topics:

27.3.1 Creating Event Interceptors

Event interceptors are created by implementing the EventInterceptor interface. The interface is defined using generics and allows you to subscribe to events by specifying the generic type of the event as a type parameter. The inherited onEvent method provides the ability to perform any necessary processing upon receiving an event. For details on the EventInterceptor API, see Java API Reference for Oracle Coherence. The following example demonstrates subscribing to all transfer events and is taken from Example 27-1:

public class RedistributionInterceptor
   implements EventInterceptor<TransferEvent>

   public void onEvent(TransferEvent event)
   {
      ...

The @Interceptor annotation can be used to further restrict the events to specific event types and also provides further configuration of the interceptor. The following example defines an interceptor identifier and restricts the events to only transfer DEPARTING events:

@Interceptor(identifier = "redist", transferEvents = TransferEvent.Type.DEPARTING)
public class RedistributionInterceptor
   implements EventInterceptor<TransferEvent>

   public void onEvent(TransferEvent event)
   {
      ...

The @Interceptor annotation includes the following attributes:

  • identifier – Specifies a unique identifier for the interceptor. The identifier can be overridden when registering an interceptor class in the cache configuration file. This attribute is optional. A unique name is automatically generated by the event infrastructure if the attribute is omitted.

  • entryEvents – Specifies an array of entry event types to which the interceptor wants to subscribe.

  • entryProcessorEvents – Specifies an array of entry processor event types to which the interceptor wants to subscribe.

  • transferEvents – Specifies an array of transfer event types to which the interceptor wants to subscribe.

  • transactionEvents – Specifies an array of transaction event types to which the interceptor wants to subscribe.

  • order – Specifies whether the interceptor is placed at the front of a chain of interceptors. See "Chaining Event Interceptors". The legal values are LOW and HIGH. A value of HIGH indicates that the interceptor is placed at the front of the chain of interceptors. A value of LOW indicates no order preference. The default value is LOW. The order can be overridden when registering an interceptor class in the cache configuration file.

The following example demonstrates a basic event interceptor implementation that subscribes to all transfer event types (both DEPARTING and ARRIVED). The onEvent method simply logs the events to show partition activity. The example is part of the Coherence examples.

Note:

Event instances are immutable and their lifecycle is controlled by the underlying system. References to event classes must not be held across multiple invocations of the onEvent() method.

Example 27-1 Example Event Interceptor Implementation

package com.tangosol.examples.events;
import com.tangosol.net.CacheFactory;
import com.tangosol.net.events.EventInterceptor;
import com.tangosol.net.events.annotation.Interceptor;
import com.tangosol.net.events.partition.TransferEvent;

@Interceptor(identifier = "redist")
public class RedistributionInterceptor
   implements EventInterceptor<TransferEvent>
   {

   public void onEvent(TransferEvent event)
      {
      CacheFactory.log(String.format("Discovered event %s for partition-id %d
         from remote member %s\n", event.getType(), event.getPartitionId(),
         event.getRemoteMember()), CacheFactory.LOG_INFO);
      }
   }

27.3.2 Understanding Event Threading

Event interceptors can have a significant impact on cache operations and must be careful not to block or otherwise affect any underlying threads. The impact for both precommit event types and postcommit event types should be carefully considered when creating event interceptors.

Note:

EventInterceptor instances can be reused; however, they should be immutable or thread-safe so that they can be dispatched by multiple threads concurrently.

Precommit Events

Precommit event types allow event interceptors to modify entries before the entries are committed to a cache. The interceptors are processed synchronously and must not perform long running operations (such as database access) that could potentially block or slow cache operations. Calls to external resource must always return as quickly as possible to avoid blocking cache operations.

Configure a thread pool for a partitioned service if event interceptors are to handle precommit events. The thread pool is disabled by default and requires setting the <thread-count> element within a distributed cache definition. For details on the <thread-count> element, see "distributed-scheme Subelements". The thread pool creates additional threads to process cache operations and helps alleviate the overall impact of event interceptors that handle precommit events, but the potential for blocking still exists.

Postcommit Events

Postcommit events do not allow an event interceptor to modify entries. The events are raised in the same order as the events occurred and the interceptors are processed asynchronously. Event interceptors that perform long running operations can cause a backlog of requests that could ultimately affect performance. It is a best practice to use the Java Executor service to perform such operations on a separate thread.

27.3.3 Registering Event Interceptors

Event interceptors are registered within a cache configuration file. Event interceptors are registered either for a specific cache or for a partitioned service. Event interceptor that are registered for a specific cache only receives events that pertain to that cache. Event interceptors that are registered for a partitioned service receives events for all caches that are managed by the service.

Note:

Event interceptors for service-level events (such as transfer events and transaction events) must be registered for a partition service and cannot be restricted to a specific cache.

This section contains the following topics:

27.3.3.1 Registering Event Interceptors For a Specific Cache

To register interceptors on a specific cache, include an <interceptors> element, within a <cache-mapping> element, that includes any number of <interceptor> subelements. Each <interceptor> element must include an <instance> subelement and provide a fully qualified class name that implements the EventInterceptor interface. See "interceptor" for a detailed reference of the <interceptor> element. The following example registers and event interceptor class called MyInterceptor.

<caching-scheme-mapping>
   <cache-mapping>
      <cache-name>example</cache-name>
      <scheme-name>distributed</scheme-name>
       <interceptors>
          <interceptor>
             <name>MyInterceptor</name>
             <instance>
                <class-name>
                   com.tangosol.examples.events.MyInterceptor
                </class-name>
             </instance>
          </interceptor>
      </interceptors>  
   </cache-mapping> 
</caching-scheme-mapping>

27.3.3.2 Registering Event Interceptors For a Partitioned Service

To register interceptors on a partitioned service, include an <interceptors> element, within a <distributed-scheme> element, that includes any number of <interceptor> subelements. Each <interceptor> element must include an <instance> subelement and provide a fully qualified class name that implements the EventInterceptor interface. See "interceptor" for a detailed reference of the <interceptor> element. The following example registers the RedistributionInterceptor class defined in Example 27-1.

<distributed-scheme>
   <scheme-name>distributed</scheme-name>
   <service-name>PartitionedService1</service-name>
   <backing-map-scheme>
      <local-scheme/>
   </backing-map-scheme>
   <autostart>true</autostart>
   <interceptors>
      <interceptor>
         <name>MyInterceptor</name>
         <instance>
            <class-name>
               com.tangosol.examples.events.RedistributionInterceptor
            </class-name>
         </instance>
      </interceptor>
   </interceptors>  
</distributed-scheme>

27.3.3.3 Using Custom Registration

The @Interceptor annotation and generic types are used by the event infrastructure to register event interceptors with the appropriate event dispatcher. This mechanism is acceptable for most uses cases. However, for advanced use cases, an event interceptor can choose to implement the EventDispatcherAwareInterceptor interface and manually register an event interceptor with the required event dispatcher.

The introduceEventDispatcher method includes the event dispatcher to which the interceptor will be registered. The dispatcher's methods are then used to add and remove interceptors, restrict specific event types, and configure the interceptor as required. The following example shows a custom implementation that explicitly registers an interceptor, subscribes to entry INSERTING events, and configures ordering to ensure that the interceptor is called and notified first:

public void introduceEventDispatcher(String sIdentifier, EventDispatcher
   dispatcher)
   {
   dispatcher.addEventInterceptor(sIdentifier, this,
      new HashSet(Arrays.asList(EntryEvent.Type.INSERTING)), true);
   }

Note:

If an interceptor is configured without using the annotations, then the configuration cannot be overridden using the cache configuration file.

Interceptors can also be programmatically registered using the InterceptorRegistry API. Registering an interceptor causes it to be introduced to all currently registered and future event dispatchers. An interceptor can determine whether or not to bind to a dispatcher by using the introduceEventDispatcher method as shown in the previous example.

The InterceptorRegistry API is available from the ConfigurableCacheFactory interface and is called using the getInterceptorRegistry method. The API can be used together with the cache configuration file when declaratively registering interceptors. The API is often used with custom DefaultCacheServer implementations to add interceptors programmatically, or the API is used to selectively register interceptors when using the InvocationService interface. The following example demonstrates registering an interceptor.

CacheFactory.getConfigurableCacheFactory().getInterceptorRegistry()
.registerEventInterceptor(new MyEventIntercepor());

27.3.3.4 Guidelines for Registering Event Interceptors

Interceptors can be registered in multiple distributed schemes for the same service. In addition, interceptor classes can be inherited if a distributed scheme uses scheme references. In both cases, the interceptor classes are registered with the service.

For most cases, registering multiple interceptor classes is not an issue. However, there are increased chances for duplicating the same interceptor classes and identifier names for a given service. The following guidelines should be followed to ensure registration errors do not occur because of duplication:

  • An interceptor class can be duplicated multiple time in a distributed scheme or in multiple schemes of the same service as long as the identifier names are unique or no identifier name is defined. For the later, a unique name is automatically generated by the event infrastructure.

  • An interceptor class (duplicated or not) with the same identifier name cannot be registered multiple times in a distributed scheme and results in a registration error.

  • An interceptor class that is inherited by scheme reference is registered once for each distinct service name in the reference chain.

27.3.4 Chaining Event Interceptors

Event interceptors that are registered for the same event type are serially called by the thread responsible for dispatching an event. The ability to chain interceptors in this manner allows complex processing scenarios where custom logic is executed based on the outcome of other interceptors in the chain. Each event interceptor in a chain can:

  • Modify data associated with the event if allowed. For example, precommit operations such as INSERTING, UPDATING, and REMOVING entry events allow data modifications.

  • Deny a precommit operations by throwing an exception.

  • Enlist a new entry into a partition-level transaction.

  • Observe the results of any side effects caused by event interceptors further down the chain. If the interceptor chain is associated with a precommitted storage event, the ability to observe the results provides a second opportunity to deny the processing.

    Observing the side effects of downstream event interceptors is accomplished using the Event.nextInterceptor method. When this method returns, it signifies that all downstream event interceptors executed and any changes to the state associated with the event can be inspected. The Event object holds state on the processing of the event. The event calls each event interceptor's onEvent method as long as there are still event interceptors in the chain. If the event interceptor itself calls the Event.nextInterceptor method, it triggers the next event interceptor in the chain to execute. If the event interceptor returns, the event itself triggers the next event interceptor in the chain's onEvent method. Either way, all event interceptors are executed, but those that choose to call the nextInterceptor method have the option of taking action based on the side effects that are observed.

27.3.4.1 Specifying an Event Interceptor Chain Order

Event interceptors in a chain are executed based on the order in which they are registered. Use the order attribute on the @Interceptor annotation to specify whether an interceptor is placed in the front of the chain. A value of HIGH places the interceptor at the front of the chain. A value of LOW indicates no order preference and is the default value. For example:

@Interceptor(identifier = "MyInterceptor",
             entryEvents = {Type.INSERTING, Type.INSERTED}
             order = Order.HIGH)
public class MyInterceptor
        implements EventInterceptor<EntryEvent>
...

The order can also be specified declaratively when registering an event interceptor in the cache configuration file and overrides the order attribute that is specified in an event interceptor class.

To specify the ordering of interceptors in the cache configuration file, include an <order> element, within an <interceptor> element, that is set to HIGH or LOW. For example:

<interceptors>
   <interceptor>
      <name>MyInterceptor</name>
      <order>HIGH</order>
      <instance>
         <class-name>package.MyInterceptor</class-name>
      </instance>
   </interceptor>
   <interceptor>
      <name>MySecondInterceptor</name>
      <instance>
         <class-name>package.MySecondInterceptor</class-name>
      </instance>
   </interceptor>
</interceptors>