25 Managing Map Operations with Triggers

Map triggers supplement the standard capabilities of Oracle Coherence to provide a highly customized cache management system. For example, map triggers can 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 Oracle 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:

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 25-1 assumes that the createMapTrigger method would return a new MapTriggerListener(new MyCustomTrigger());:

Example 25-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, Oracle 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 25-2, MapTriggerListener is used to register the PersonMapTrigger with the People named cache.

Example 25-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 the Javadoc pages for MapTrigger, MapTrigger.Entry, FilterTrigger, and MapTriggerListener.

25.1 A Map Trigger Example

The code in Example 25-3 illustrates a map trigger and how it can be called. In the PersonMapTrigger class in Example 25-3, 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 25-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 25-4, calls the PersonMapTrigger. The new MapTriggerListener passes the PersonMapTrigger to the People NamedCache.

Example 25-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");
        }
    }