26 Controlling Map Operations with Triggers

This chapter provides instructions for using map triggers to validate, reject, or modify map operations before a change is committed to a map entry.

This chapter contains the following sections:

26.1 Overview of Map Triggers

Map triggers supplement the standard capabilities of Coherence to provide a highly customized cache management system. Map triggers can be used to prevent invalid transactions, enforce complex security authorizations or complex business rules, provide transparent event logging and auditing, and gather statistics on data modifications. Other possible use for triggers include restricting operations against a cache to those issued during application re-deployment time.

For example, assume that you have code that is working with a NamedCache, and you want to change an entry's behavior or contents before the entry is inserted into the map. The addition of a map trigger enables you to make this change without having to modify all the existing code.

Map triggers could also be used as part of an upgrade process. The addition of a map trigger could prompt inserts to be diverted from one cache into another.

A map trigger in the Coherence cache is somewhat similar to a trigger that might be applied to a database. It is a functional agent represented by the MapTrigger interface that is run in response to a pending change (or removal) of the corresponding map entry. The pending change is represented by the MapTrigger.Entry interface. This interface inherits from the InvocableMap.Entry interface, so it provides methods to retrieve, update, and remove values in the underlying map.

The MapTrigger interface contains the process method that is used to validate, reject, or modify the pending change in the map. This method is called before an operation that intends to change the underlying map content is committed. An implementation of this method can evaluate the pending change by analyzing the original and the new value and produce any of the following results:

  • override the requested change with a different value

  • undo the pending change by resetting the original value

  • remove the entry from the underlying map

  • reject the pending change by throwing a runtime exception

  • do nothing, and allow the pending change to be committed

MapTrigger functionality is typically added as part of an application start-up process. It can be added programmatically as described in the MapTrigger API, or it can be configured using the class-factory mechanism in the coherence-cache-config.xml configuration file. In this case, a MapTrigger is registered during the very first CacheFactory.getCache(...) call for the corresponding cache. Example 26-1 assumes that the createMapTrigger method would return a new MapTriggerListener(new MyCustomTrigger());:

Example 26-1 Example MapTriggerListener Configuration

<distributed-scheme>
   ...
   <listener>
      <class-scheme>
         <class-factory-name>package.MyFactory</class-factory-name>
         <method-name>createTriggerListener</method-name>
         <init-params>
            <init-param>
               <param-type>string</param-type>
               <param-value>{cache-name}</param-value>
            </init-param>
         </init-params>
      </class-scheme>
   </listener>
</distributed-scheme>

In addition to the MapTrigger.Entry and MapTrigger interfaces, Coherence provides the FilterTrigger and MapTriggerListener classes. The FilterTrigger is a generic MapTrigger implementation that performs a predefined action if a pending change is rejected by the associated Filter. The FilterTrigger can either reject the pending operation, ignore the change and restore the entry's original value, or remove the entry itself from the underlying map.

The MapTriggerListener is a special purpose MapListener implementation that is used to register a MapTrigger with a corresponding NamedCache. In Example 26-2, MapTriggerListener is used to register the PersonMapTrigger with the People named cache.

Example 26-2 A MapTriggerListener Registering a MapTrigger with a Named Cache

NamedCache person = CacheFactory.getCache("People");
MapTrigger trigger = new PersonMapTrigger();
person.addMapListener(new MapTriggerListener(trigger));

These API reside in the com.tangosol.util package. For more information on these API, see Java API Reference for Oracle Coherence.

26.2 A Map Trigger Example

The code in Example 26-3 illustrates a map trigger and how it can be called. In the PersonMapTrigger class, the process method is implemented to modify an entry before it is placed in the map. In this case, the last name attribute of a Person object is converted to upper case characters. The object is then returned to the entry.

Example 26-3 A MapTrigger Class

...

public class PersonMapTrigger implements MapTrigger 
    {
    public PersonMapTrigger()
        {
        }

    public void process(MapTrigger.Entry entry)
        {
        Person person  = (Person) entry.getValue();
        String sName   = person.getLastName();
        String sNameUC = sName.toUpperCase();
        
        if (!sNameUC.equals(sName))
           { 
           person.setLastName(sNameUC);
        
           System.out.println("Changed last name of [" + sName + "] to [" + person.getLastName() + "]");
        
           entry.setValue(person);
           }
        }

    // ---- hashCode() and equals() must be implemented

    public boolean equals(Object o)
        {
        return o != null && o.getClass() == this.getClass();
        }
    public int hashCode()
        {
        return getClass().getName().hashCode();
        }
    }

The MapTrigger in Example 26-4, calls the PersonMapTrigger. The new MapTriggerListener passes the PersonMapTrigger to the People NamedCache.

Example 26-4 Calling a MapTrigger and Passing it to a Named Cache

...

public class MyFactory
    {
    /**
    * Instantiate a MapTriggerListener for a given NamedCache
    */
    public static MapTriggerListener createTriggerListener(String sCacheName)
        {
        MapTrigger trigger;
        if ("People".equals(sCacheName))
            {
            trigger = new PersonMapTrigger();
            }
        else
            {
            throw IllegalArgumentException("Unknown cache name " + sCacheName);
            }

        System.out.println("Creating MapTrigger for cache " + sCacheName);

        return new MapTriggerListener(trigger);
        }

    public static void main(String[] args) 
        {
        NamedCache cache = CacheFactory.getCache("People");
        cache.addMapListener(createTriggerListener("People"));

        System.out.println("Installed MapTrigger into cache People");
        }
    }