Coherenceでは、JavaBeanイベント・モデルを使用してキャッシュ・イベントが提供されます。変更が実際にはクラスタ内のどこで発生していても、とても簡単な方法で必要なイベントを必要なときに受信できます。JavaBeanモデルを扱ったことのある開発者であれば、複雑なクラスタであってもイベントに難無く対応できるはずです。
JavaBeansイベント・モデルには、すべてのリスナーで拡張する必要があるEventListenerインタフェースがあります。Coherenceには、MapListenerインタフェースが用意されており、これによりCoherenceキャッシュのデータが追加、変更または削除されたときにアプリケーション・ロジックでイベントを受信できます。
例19-1は、com.tangosol.util.MapListener APIからの抜粋を示しています。
例19-1 MapListener APIからの抜粋
public interface MapListener
        extends EventListener
    {
    /**
    * Invoked when a map entry has been inserted.
    *
    * @param evt  the MapEvent carrying the insert information
    */
    public void entryInserted(MapEvent evt);
    /**
    * Invoked when a map entry has been updated.
    *
    * @param evt  the MapEvent carrying the update information
    */
    public void entryUpdated(MapEvent evt);
    /**
    * Invoked when a map entry has been removed.
    *
    * @param evt  the MapEvent carrying the delete information
    */
    public void entryDeleted(MapEvent evt);
    }
MapListenerインタフェースを実装するアプリケーション・オブジェクトは、アプリケーションのMapListener実装のインスタンスをaddMapListener()メソッドのいずれかに渡すだけで、ObservableMapインタフェースを実装する任意のCoherenceキャッシュまたはクラスからイベントにサインアップできます。
MapListenerに渡されたMapEventオブジェクトは、発生済のイベントについて必要なすべての情報を伝達します。それには、イベントを起動するソース(ObservableMap)、そのイベントに関連付けられたID(キー)、そのIDに対して実行されたアクション(挿入、更新、または削除)、以前の値や新しい値などが含まれます。
例19-2は、com.tangosol.util.MapEvent APIからの抜粋を示しています。
例19-2 MapEvent APIからの抜粋
public class MapEvent
        extends EventObject
    {
    /**
    * Return an ObservableMap object on which this event has actually
    * occurred.
    *
    * @return an ObservableMap object
    */
    public ObservableMap getMap()
    /**
    * Return this event's id. The event id is one of the ENTRY_*
    * enumerated constants.
    *
    * @return an id
    */
    public int getId()
    /**
    * Return a key associated with this event.
    *
    * @return a key
    */
    public Object getKey()
    /**
    * Return an old value associated with this event.
    * <p>
    * The old value represents a value deleted from or updated in a map.
    * It is always null for "insert" notifications.
    *
    * @return an old value
    */
    public Object getOldValue()
    /**
    * Return a new value associated with this event.
    * <p>
    * The new value represents a new value inserted into or updated in
    * a map. It is always null for "delete" notifications.
    *
    * @return a new value
    */
    public Object getNewValue()
    // ----- Object methods -------------------------------------------------
    /**
    * Return a String representation of this MapEvent object.
    *
    * @return a String representation of this MapEvent object
    */
    public String toString()
    // ----- constants ------------------------------------------------------
    /**
    * This event indicates that an entry has been added to the map.
    */
    public static final int ENTRY_INSERTED = 1;
    /**
    * This event indicates that an entry has been updated in the map.
    */
    public static final int ENTRY_UPDATED  = 2;
    /**
    * This event indicates that an entry has been removed from the map.
    */
    public static final int ENTRY_DELETED  = 3;
    }
すべてのCoherenceキャッシュには、ObservableMapが実装されます。実際に、すべてのCoherenceキャッシュで実装されるNamedCacheインタフェースは、ObservableMapインタフェースを拡張します。つまり、キャッシュが、ローカル、パーティション、ニア、レプリケーションであるかどうか、また、リードスルー、ライトスルー、ライトビハインド、オーバーフロー、ディスク記憶域などを使用しているかどうかに関係なく、アプリケーションはサインアップして任意のキャッシュからイベントを受信できます。
| 注意:キャッシュ・トポロジやサーバーの数に関係なく、また他のサーバーが変更している場合でも、これらのイベントはアプリケーションのリスナーに配信されます。 | 
Coherenceキャッシュ(Coherenceキャッシュ・ファクトリを介して取得されたオブジェクト)に加えて、次のように、Coherenceでサポートされる他のいくつかのクラスでもObservableMapインタフェースが実装されます。
ObservableHashMap
LocalCache
OverflowMap
NearCache
ReadWriteBackingMap
AbstractSerializationCache、SerializationCacheおよびSerializationPagedCache
WrapperObservableMap、WrapperConcurrentMapおよびWrapperNamedCache
公開されている実装クラスの一覧は、Coherence Javadocの「ObservableMap」を参照してください。
イベントにサインアップするには、MapListenerインタフェースを実装するオブジェクトをObservableMapのaddMapListenerメソッドの1つに渡します。例19-3は、addMapListenerメソッドを示しています。
例19-3 ObservableMap APIのメソッド
public void addMapListener(MapListener listener); public void addMapListener(MapListener listener, Object oKey, boolean fLite); public void addMapListener(MapListener listener, Filter filter, boolean fLite);
MapListener実装のサンプルを作成してみましょう。例19-4は、イベントを受信したときにそれぞれのイベントを出力するMapListener実装のサンプルを示しています。
例19-4 MapListener実装のサンプル
/**
* A MapListener implementation that prints each event as it receives
* them.
*/
public static class EventPrinter
        extends Base
        implements MapListener
    {
    public void entryInserted(MapEvent evt)
        {
        out(evt);
        }
    public void entryUpdated(MapEvent evt)
        {
        out(evt);
        }
    public void entryDeleted(MapEvent evt)
        {
        out(evt);
        }
    }
この実装を使用すると、任意のキャッシュからすべてのイベントを非常に簡単に出力できます(すべてのキャッシュでObservableMapインタフェースが実装されているため)。
cache.addMapListener(new EventPrinter());
ただし、後でリスナーを削除できるように、リスナーへの参照を保持する必要があります。
例19-5 リスナーへの参照の保持
Listener listener = new EventPrinter(); cache.addMapListener(listener); m_listener = listener; // store the listener in a field
後でリスナーを削除するには、次のように処理します。
例19-6 リスナーの削除
Listener listener = m_listener;
if (listener != null)
    {
    cache.removeMapListener(listener);
    m_listener = null; // clean up the listener field
    }
ObservableMapインタフェースの各addMapListenerメソッドには、対応するremoveMapListenerメソッドがあります。リスナーを削除するには、そのリスナーの追加に使用したaddMapListenerメソッドに対応するremoveMapListenerメソッドを使用します。
MapListenerとして使用する内部クラスを作成する場合、または1つか2つのイベント・タイプ(挿入、更新、または削除)のみをリスニングするMapListenerを実装する場合、AbstractMapListenerベース・クラスを使用できます。たとえば、例19-7の無名の内部クラスでは、キャッシュの挿入イベントのみを出力します。
例19-7 キャッシュの挿入イベントのみを出力する内部クラス
cache.addMapListener(new AbstractMapListener()
    {
    public void entryInserted(MapEvent evt)
        {
        out(evt);
        }
    });
MapListenerの作成に役立つもう1つのベース・クラスは、MultiplexingMapListenerです。これは、すべてのイベントを1つのメソッドにルーティングして処理します。このクラスを使用すると、EventPrinterのサンプルを例19-8のコードのように簡略化できます。1つのメソッドを実装するだけですべてのイベントを取得できるため、MultiplexingMapListenerは、MapListenerとして使用する内部クラスを作成する際にも非常に便利です。
特定のキャッシュにリスナーを常駐させる必要がある場合、<listener>要素を使用して、それをキャッシュ構成に配置します。キャッシュ構成の際に、リスナーは自動的に追加されます。
特定のID(キー)に対して発生したイベントのサインアップは非常に簡単です。たとえば、整数キー5に対して発生したすべてのイベントを出力するには、次のように処理します。
cache.addMapListener(new EventPrinter(), new Integer(5), false);
そのため、例19-9のコードでは、整数キー5が挿入または更新された場合にのみイベントがトリガーされます。
特定のキーのリスニングと同様に、特定のイベントをリスニングすることもできます。例19-10では、削除イベントのみを受信できるフィルタが指定されたリスナーがキャッシュに追加されます。
例19-10 削除されたイベントのフィルタを指定したリスナーの追加
// Filters used with partitioned caches must be 
// Serializable, Externalizable or ExternalizableLite
public class DeletedFilter
        implements Filter, Serializable
    {
    public boolean evaluate(Object o)
        {
        MapEvent evt = (MapEvent) o;
        return evt.getId() == MapEvent.ENTRY_DELETED;
        }
    }
cache.addMapListener(new EventPrinter(), new DeletedFilter(), false);
| 注意:イベントのフィルタリングとキャッシュされたデータのフィルタリングの比較 問合せのFilterを作成する場合、Filterのevaluateメソッドに渡すオブジェクトはキャッシュからの値になります。または、FilterでEntryFilterインタフェースを実装する場合は、キャッシュからのMap.Entry全体になります。MapListenerのイベントをフィルタリングするFilterを作成する場合、Filterのevaluateメソッドに渡すオブジェクトは常にMapEvent型になります。 問合せフィルタを使用してキャッシュ・イベントをリスニングする方法の詳細は、「応用: 問合せのリスニング」を参照してください。 | 
次の一連のコールを実行すると、
cache.put("hello", "world");
cache.put("hello", "again");
cache.remove("hello");
次のような結果になります。
CacheEvent{LocalCache deleted: key=hello, value=again}
詳細は、「応用: 問合せのリスニング」を参照してください。
デフォルトでは、イベントの一部として古い値と新しい値が両方とも提供されます。次の例があります。
例19-11 キャッシュにおける値の挿入、更新、削除
MapListener listener = new MultiplexingMapListener()
    {
    public void onMapEvent(MapEvent evt)
        {
        out("event has occurred: " + evt);
        out("(the wire-size of the event would have been "
            + ExternalizableHelper.toBinary(evt).length()
            + " bytes.)");
        }
    };
cache.addMapListener(listener);
// insert a 1KB value
cache.put("test", new byte[1024]);
// update with a 2KB value
cache.put("test", new byte[2048]);
// remove the 2KB value
cache.remove("test");
例19-12に示すテストの実行結果の出力では、1つ目のイベントは挿入される値(1KB)を保持し、2つ目のイベントは置換前の値(1KB)と新しい値(2KB)を保持しています。3つ目のイベントは、削除される値(2KB)を保持しています。
例19-12 サンプル出力
event has occurred: CacheEvent{LocalCache added: key=test, value=[B@a470b8}
(the wire-size of the event would have been 1283 bytes.)
event has occurred: CacheEvent{LocalCache updated: key=test, old value=[B@a470b8, new value=[B@1c6f579}
(the wire-size of the event would have been 3340 bytes.)
event has occurred: CacheEvent{LocalCache deleted: key=test, value=[B@1c6f579}
(the wire-size of the event would have been 2307 bytes.)
アプリケーションでイベントに古い値と新しい値を両方とも含める必要がない場合、Liteイベントのみをリクエストして、そのように指定することができます。リスナーの追加時にLiteイベントをリクエストするには、追加のブール・パラメータfLiteを使用する2つのaddMapListenerメソッドのいずれかを使用できます。例19-11の場合は、次の箇所のみを変更します。
cache.addMapListener(listener, (Filter) null, true);
| 注意:Liteイベントの古い値と新しい値は、NULLにすることができます。ただし、Liteイベントをリクエストしても、イベントの生成と配信に余分な負荷がかからない場合は、古い値と新しい値が含まれることがあります。つまり、 MapListenerでLiteイベントを受信するようにリクエストしても、それは単にシステムにMapListenerがイベントの古い値と新しい値を認識する必要がないことを知らせているだけになります。 | 
すべてのCoherenceキャッシュはどの基準による問合せもサポートしています。アプリケーションがキャッシュのデータを問い合せると、その結果は一連の識別子(keySet)または一連の識別子と値の組合せ(entrySet)のいずれかとして、ポイント・イン・タイム・スナップショットで返されます。結果セットの内容を判断するメカニズムは、フィルタリングと呼ばれ、これによりアプリケーションの開発者は豊富な既定のフィルタ(equals、less-than、like、betweenなど)を使用してどのように複雑な問合せも構築でき、また独自のカスタム・フィルタ(XPathなど)を提供できるようになります。
キャッシュの問合せに使用するフィルタと同じフィルタを、キャッシュからのイベントのリスニングに使用することもできます。たとえば、取引システムにおいて、特定のトレーダーが受け持つ未決済のOrderオブジェクトをすべて問い合せることができます。
例19-13 キャッシュからのイベントのリスニング
NamedCache mapTrades = ...
Filter filter = new AndFilter(new EqualsFilter("getTrader", traderid),
                              new EqualsFilter("getStatus", Status.OPEN));
Set setOpenTrades = mapTrades.entrySet(filter);
そのトレーダーが新しいポジションを建てた、そのトレーダーがポジションを決済した、トレーダー間でポジションを付け替えた、という情報を受信するために、アプリケーションで同じフィルタを使用できます。
例19-14 オブジェクトのイベントのリスニング
// receive events for all trade IDs that this trader is interested in mapTrades.addMapListener(listener, new MapEventFilter(filter), true);
MapEventFilterは、問合せフィルタをイベント・フィルタに変換します。
MapEventFilterには、いくつもの非常に強力なオプションがあります。これにより、アプリケーション・リスナーは特に関心のあるイベントのみを受信できます。スケーラビリティとパフォーマンスの観点からさらに重要なこととして、必要なイベントのみがネットワーク上で通信されるため、それらの特定イベントを対象とするサーバーとクライアントにのみ配信されます。例19-15は、これらのシナリオを示しています。
例19-15 MapEventFilterを使用した様々なイベントのフィルタリング
// receive all events for all trades that this trader is interested in nMask = MapEventFilter.E_ALL; mapTrades.addMapListener(listener, new MapEventFilter(nMask, filter), true); // receive events for all this trader's trades that are closed or // re-assigned to a different trader nMask = MapEventFilter.E_UPDATED_LEFT | MapEventFilter.E_DELETED; mapTrades.addMapListener(listener, new MapEventFilter(nMask, filter), true); // receive events for all trades as they are assigned to this trader nMask = MapEventFilter.E_INSERTED | MapEventFilter.E_UPDATED_ENTERED; mapTrades.addMapListener(listener, new MapEventFilter(nMask, filter), true); // receive events only fornew trades assigned to this trader nMask = MapEventFilter.E_INSERTED; mapTrades.addMapListener(listener, new MapEventFilter(nMask, filter), true);
サポートされる各種オプションの詳細は、MapEventFilterのAPIドキュメントを参照してください。
問合せのFilterを作成する場合、Filterのevaluateメソッドに渡すオブジェクトはキャッシュからの値になります。または、FilterでEntryFilterインタフェースを実装する場合は、キャッシュからのMap.Entry全体になります。MapListenerのイベントをフィルタリングするFilterを作成する場合、Filterのevaluateメソッドに渡すオブジェクトは常にMapEvent型になります。
MapEventFilterは、問合せに使用するFilterをMapListenerのイベントのフィルタリングに使用するFilterに変換します。つまり、MapEventFilterはキャッシュを問い合せるFilterから作成されますが、作成されたMapEventFilterは、問合せFilterが期待するオブジェクトに変換することによってMapEventオブジェクトを評価するフィルタになります。
イベントは通常、キャッシュに対する変更を反映します。たとえば、あるサーバーがキャッシュ内の1つのエントリを変更し、別のサーバーがキャッシュに複数のアイテムを追加し、3つ目のサーバーが同じキャッシュから1つのアイテムを削除している間に、クラスタ内の各サーバーで50個のスレッドが同じキャッシュのデータにアクセスすることがあります。すべての変更操作によりイベントが生成されますが、それをクラスタ内の任意のサーバーで受信するように選択できます。これらの操作をクライアント・アクションと呼び、そのイベントは、クライアントにディスパッチされた状態にあります(この場合の「クライアント」が実際には「サーバー」であっても)。これはCoherenceクラスタなどの、真のpeer-to-peerアーキテクチャでは自然の概念です。いずれのピアもクライアントでありサーバーでもあるので、他のピアからサービスを受け、他のピアにサービスを提供します。一般的なJava Enterpriseアプリケーションでは、ピアはアプリケーションのコンテナとして動作するアプリケーション・サーバー・インスタンスで、クライアントはキャッシュに対して直接アクセスおよび変更を行い、キャッシュからのイベントをリスニングするアプリケーションの一部です。
イベントの中には、キャッシュ内部で起動されるものもあります。多くの例がありますが、最も一般的なケースは次のとおりです。
キャッシュのエントリが自動失効した場合
キャッシュの最大サイズに達したため、エントリがキャッシュから削除された場合
リードスルー操作の結果としてエントリが透過的にキャッシュに追加された場合
リードアヘッドまたはリフレッシュアヘッド操作の結果としてキャッシュ内のエントリが透過的に更新された場合
これらはそれぞれ変更の例ですが、この場合の変更とはキャッシュ内部での自然な(通常は自動的な)操作を表します。このようなイベントは統合イベントと呼ばれます。
必要に応じて、アプリケーションはイベントに問い合せて、それがクライアントに起因するイベントか、または統合イベントかを区別できます。この情報は、CacheEventというMapEventのサブクラスで保持されます。前述のEventPrinterの例を使用して、統合イベントのみを出力できます。
例19-16 統合イベントの判断
public static class EventPrinter
        extends MultiplexingMapListener
    {
    public void onMapEvent(MapEvent evt)
        {
        if (evt instanceof CacheEvent && ((CacheEvent) evt).isSynthetic())
            {
            out(evt);
            )
        }
    }
この機能の詳細は、CacheEventのAPIドキュメントを参照してください。
Coherenceキャッシュのイベントをリスニングし、データに関して分散、パーティション、レプリケーション、ニア・キャッシュ、連続問合せ、リードスルー/ライトスルー、ライトビハインドのローカル表示を確認できる一方で、いわば「こっそりのぞき見る」こともできます。
いくつかの応用例の中には、サービスの背後でマップをリスニングする必要がある場合があります。分散環境でのレプリケーション、パーティション、およびその他のデータ管理方式は、いずれも分散サービスです。このサービスにはさらに実際にデータを管理するための機能が必要です。それが「バッキング・マップ」と呼ばれるものです。
バッキング・マップを構成できます。特定のキャッシュのすべてのデータを、ヒープのオブジェクト形式で保持する必要がある場合、無制限で失効期限のないLocalCache(統計が不要な場合はSafeHashMap)を使用します。メモリーに保持するアイテムの数が少ない場合は、LocalCacheを使用します。データを必要に応じてデータベースから読み取る場合は、ReadWriteBackingMapを使用します(アプリケーションのDAO実装を介した読取りおよび書込み方法を指定します)。ReadWriteBackingMapに、SafeHashMapやLocalCacheなどのバッキング・マップを提供してデータを保存します。
バッキング・マップの中には監視可能なものがあります。これらのバッキング・マップから取得されたイベントは、一般的にはアプリケーションに直接関係がありません。かわりに、Coherenceではそれらを(Coherenceで)実行する必要があるアクションに変換してデータを同期的に保持し正しくバックアップを行い、またアプリケーション・リスナーでのリクエストに応じてそれをクラスタ全体に配信されるクラスタ化されたイベントに変換します。たとえば、パーティション・キャッシュにバッキング・マップとしてLocalCacheがあり、ローカル・キャッシュでエントリが失効した場合、そのイベントによってエントリのすべてのバックアップ・コピーが失効します。さらに、リスナーがパーティション・キャッシュに登録されている場合、イベントがイベント・フィルタに適合すると、そのイベントはそのリスナーが登録されているサーバーのリスナーに配信されます。
応用例の中には、データが保持されているサーバー上のイベントをアプリケーションで処理する必要があるものや、それを実際にデータを管理している構造(バッキング・マップ)で行う必要があるものがあります。そのような場合、バッキング・マップが監視可能なマップであれば、リスナーをバッキング・マップに構成したり、プログラムによってリスナーをバッキング・マップに追加することができます (バッキング・マップが監視可能でない場合、WrapperObservableMapでラップすることにより監視可能になります)。
この機能の詳細は、BackingMapManagerのAPIドキュメントを参照してください。
バッキングMapListenerイベントは、読取り可能なJavaフォーマットでレプリケーション・キャッシュから返されます。ただし、分散キャッシュから返されたバッキングMapListenerイベントは、Coherenceの内部フォーマットになります。Coherence Incubator Commonプロジェクトには、分散キャッシュから読取り可能なバッキングMapListenerイベントから取得できるAbstractMultiplexingBackingMapListenerクラスが用意されています。Coherence Commonライブラリをダウンロードするには、http://coherence.oracle.com/display/INCUBATOR/Coherence+Commonを参照してください。 
分散キャッシュからの読取り可能なバッキングMapListenerイベントを生成するには:
AbstractMultiplexingBackingMapListenerクラスを実装します。
cache-configファイルのbacking-map-schemeの<listener>セクションに実装を登録します。
Javaプロパティのcacheconfigで、キャッシュ・サーバー・アプリケーション・ファイルおよびクライアント・ファイルを開始します。
-Dtangosol.coherence.cacheconfig="cache-config.xml"
AbstractMultiplexingBackingMapListenerクラスには、onBackingMapEventメソッドが用意されています。これを上書きしてイベントが返される方法を指定できます。
次のVerboseBackingMapListenerクラスの一覧は、AbstractMultiplexingBackingMapListenerのサンプルの実装です。onBackingMapEventメソッドは、結果を標準出力に送信するように上書きされています。
例19-17 AbstractMultiplexingBackingMapListener実装
import com.tangosol.net.BackingMapManagerContext;
import com.tangosol.util.MapEvent;
public class VerboseBackingMapListener extends AbstractMultiplexingBackingMapListener {
        public VerboseBackingMapListener(BackingMapManagerContext context) {
                super(context);
        }
        
        @Override
        protected void onBackingMapEvent(MapEvent mapEvent, Cause cause) {
                
                System.out.printf("Thread: %s Cause: %s Event: %s\n", Thread.currentThread().getName(), cause, mapEvent);
                
                try {
                        Thread.currentThread().sleep(5000);
                } catch (InterruptedException e) {
                        // add Auto-generated catch block
                 e.printStackTrace();
                }
                
        }
}
例19-18は、cache-config.xmlファイルのサンプルの一覧です。ファイルの<listener>セクションで、VerboseBackingMapListenerは、com.tangosol.net.BackingMapManagerContextタイプとして識別されます。
例19-18 詳細バッキング・マップ・リスナーを指定するキャッシュ構成
<cache-config>
    <distributed-scheme>
        <scheme-name>my-dist-scheme</scheme-name>
        <service-name>DistributedCache</service-name>
        <backing-map-scheme>
                <read-write-backing-map-scheme>
                        <internal-cache-scheme>
                                <local-scheme>
                                        <high-units>0</high-units>
                                        <expiry-delay>0</expiry-delay>
                                </local-scheme>
                        </internal-cache-scheme>
      
                        <cachestore-scheme>
                                <class-scheme>
                                        <class-name>CustomCacheStore</class-name>
                                        <init-params>
                                                <init-param>
                                                        <param-type>java.lang.String</param-type>
                                                        <param-value>{cache-name}</param-value>
                                                </init-param>
                                        </init-params>
                                </class-scheme>
                        </cachestore-scheme>
                                                                
                        <listener> 
                               <class-scheme>
                               <class-name>VerboseBackingMapListener</class-name>
                                        <init-params>
                                                <init-param>
                                                        <param-type>com.tangosol.net.BackingMapManagerContext</param-type>
                                                        <param-value>{manager-context}</param-value>
                                                </init-param>
                                        </init-params>
                                </class-scheme>
                                </listener> 
                </read-write-backing-map-scheme>                                            
        </backing-map-scheme>
        <autostart>true</autostart>
    </distributed-scheme>
...
</cache-config>
イベントの中には、アプリケーション・リスナーがイベントを生成するキャッシュ・サービスを妨害しないように、非同期に配信されるものがあります。まれなケースですが、非同期配信では進行している処理の結果に比べてイベントの順序があいまいになる場合があります。クラスタ・システムのローカル表示が単一スレッドであるかのようにキャッシュAPI操作およびイベントの順序を保証するには、MapListenerでSynchronousListenerマーカー・インタフェースを実装する必要があります。
Coherence自体で同期リスナーを使用する例にニア・キャッシュがあります。これにより、ローカルでキャッシュされたデータを、イベントを使用して無効化できます(通称、Seppuku)。
この機能の詳細は、MapListenerSupport.SynchronousListenerのAPIドキュメントを参照してください。