Configuration and Usage

General Instructions

Configuring and using Coherence for .NET requires five basic steps:

  1. Configure Coherence*Extend on both the client and on one or more JVMs within the cluster.
  2. Configure a POF context on the client and on all of the JVMs within the cluster that are running the Coherence*Extend clustered service.
  3. Implement the .NET client application using the Coherence for .NET API.
  4. Make sure the Coherence cluster is up and running.
  5. Launch the .NET client application.

The following sections describe each of these steps in detail.

Configuring Coherence*Extend

To configure Coherence*Extend, you need to add the appropriate configuration elements to both the cluster and client-side cache configuration descriptors. The cluster-side cache configuration elements instruct a Coherence DefaultCacheServer to start a Coherence*Extend clustered service that will listen for incoming TCP/IP requests from Coherence*Extend clients. The client-side cache configuration elements are used by the client library to determine the IP address and port of one or more servers in the cluster that are running the Coherence*Extend clustered service so that it can connect to the cluster. It also contains various connection-related parameters, such as connection and request timeouts.

Configuring Coherence*Extend in the Cluster

In order for a Coherence*Extend client to connect to a Coherence cluster, one or more DefaultCacheServer JVMs within the cluster must run a TCP/IP Coherence*Extend clustered service. To configure a DefaultCacheServer to run this service, a proxy-scheme element with a child tcp-acceptor element must be added to the cache configuration descriptor used by the DefaultCacheServer. For example:

<?xml version="1.0"?>
<!DOCTYPE cache-config SYSTEM "cache-config.dtd">

<cache-config>
  <caching-scheme-mapping>
    <cache-mapping>
      <cache-name>dist-*</cache-name>
      <scheme-name>dist-default</scheme-name>
    </cache-mapping>
  </caching-scheme-mapping>

  <caching-schemes>
    <distributed-scheme>
      <scheme-name>dist-default</scheme-name>
      <lease-granularity>member</lease-granularity>
      <backing-map-scheme>
        <local-scheme/>
      </backing-map-scheme>
      <autostart>true</autostart>
    </distributed-scheme>

    <proxy-scheme>
      <service-name>ExtendTcpProxyService</service-name>
      <thread-count>5</thread-count>
      <acceptor-config>
        <tcp-acceptor>
          <local-address>
            <address>localhost</address>
            <port>9099</port>
          </local-address>
        </tcp-acceptor>
      </acceptor-config>
      <autostart>true</autostart>
    </proxy-scheme>
  </caching-schemes>
</cache-config>

This cache configuration descriptor defines two clustered services, one that allows remote Coherence*Extend clients to connect to the Coherence cluster over TCP/IP and a standard Partitioned cache service. Since this descriptor is used by a DefaultCacheServer, it is important that the autostart configuration element for each service is set to true so that clustered services are automatically restarted upon termination. The proxy-scheme element has a tcp-acceptor child element which includes all TCP/IP-specific information needed to accept client connection requests over TCP/IP.

The Coherence*Extend clustered service configured above will listen for incoming requests on the localhost address and port 9099. When, for example, a client attempts to connect to a Coherence cache called "dist-extend", the Coherence*Extend clustered service will proxy subsequent requests to the NamedCache with the same name which, in this example, will be a Partitioned cache.

Configuring Coherence*Extend on the Client

A Coherence*Extend client uses the information within an initiator-config cache configuration descriptor element to connect to and communicate with a Coherence*Extend clustered service running within a Coherence cluster. For example:

<?xml version="1.0"?>

<cache-config xmlns="http://schemas.tangosol.com/cache">
  <caching-scheme-mapping>
    <cache-mapping>
      <cache-name>dist-extend</cache-name>
      <scheme-name>extend-dist</scheme-name>
    </cache-mapping>
  </caching-scheme-mapping>

  <caching-schemes>
    <remote-cache-scheme>
      <scheme-name>extend-dist</scheme-name>
      <service-name>ExtendTcpCacheService</service-name>
      <initiator-config>
        <tcp-initiator>
          <remote-addresses>
            <socket-address>
              <address>localhost</address>
              <port>9099</port>
            </socket-address>
          </remote-addresses>
        </tcp-initiator>
        <outgoing-message-handler>
          <request-timeout>5s</request-timeout>
        </outgoing-message-handler>
      </initiator-config>
    </remote-cache-scheme>
  </caching-schemes>
</cache-config>

This cache configuration descriptor defines a caching scheme that connects to a remote Coherence cluster. The remote-cache-scheme element has a tcp-initiator child element which includes all TCP/IP-specific information needed to connect the client with the Coherence*Extend clustered service running within the remote Coherence cluster.

When the client application retrieves a named cache via the CacheFactory using, for example, the name "dist-extend", the Coherence*Extend client will connect to the Coherence cluster via TCP/IP (using the address "localhost" and port 9099) and return a INamedCache implementation that routes requests to the NamedCache with the same name running within the remote cluster. Note that the remote-addresses configuration element can contain multiple socket-address child elements. The Coherence*Extend client will attempt to connect to the addresses in a random order, until either the list is exhausted or a TCP/IP connection is established.

Connection Error Detection and Failover

When a Coherence*Extend client service detects that the connection between the client and cluster has been severed (for example, due to a network, software, or hardware failure), the Coherence*Extend client service implementation (i.e. ICacheService or IInvocationService) will raise a MemberEventType.Left event (via the MemberEventHandler delegate) and the service will be stopped. If the client application attempts to subsequently use the service, the service will automatically restart itself and attempt to reconnect to the cluster. If the connection is successful, the service will raise a MemberEventType.Joined event; otherwise, a fatal exception will be thrown to the client application.

A Coherence*Extend service has several mechanisms for detecting dropped connections. Some mechanisms are inherit to the underlying protocol (i.e. TCP/IP in Extend-TCP), whereas others are implemented by the service itself. The latter mechanisms are configured via the outgoing-message-handler configuration element.

The primary configurable mechanism used by a Coherence*Extend client service to detect dropped connections is a request timeout. When the service sends a request to the remote cluster and does not receive a response within the request timeout interval (see <request-timeout>), the service assumes that the connection has been dropped. The Coherence*Extend client and clustered services can also be configured to send a periodic heartbeat over the connection (see <heartbeat-interval> and <heartbeat-timeout>). If the service does not receive a response within the configured heartbeat timeout interval, the service assumes that the connection has been dropped.

Configuring a POF Context

Coherence caches are used to cache value objects. Enabling .NET clients to successfully communicate with a Coherence JVM requires a platform-independent serialization format that allows both .NET clients and Coherence JVMs (including Coherence*Extend Java clients) to properly serialize and deserialize value objects stored in Coherence caches. The Coherence for .NET client library and Coherence*Extend clustered service use a serialization format known as Portable Object Format (POF). POF allows value objects to be encoded into a binary stream in such a way that the platform and language origin of the object is irrelevant.

POF supports all common .NET and Java types out-of-the-box. Any custom .NET and Java class can also be serialized to a POF stream; however, there are additional steps required to do so:

  1. Create a .NET class that implements the IPortableObject interface.
  2. Create a matching Java class that implements the PortableObject interface in the same way.
  3. Register your custom .NET class on the client.
  4. Register your custom Java class on each of the servers running the Coherence*Extend clustered service.

Once these steps are complete, you can cache your custom .NET classes in a Coherence cache in the same way as a built-in data type. Additionally, you will be able to retrieve, manipulate, and store these types from a Coherence or Coherence*Extend JVM using the matching Java classes.

Creating an IPortableObject Implementation (.NET)

Each class that implements IPortableObject is capable of self-serializing and deserializing its state to and from a POF data stream. This is achieved in the ReadExternal (deserialize) and WriteExternal (serialize) methods. Conceptually, all user types are composed of zero or more indexed values (properties) which are read from and written to a POF data stream one by one. The only requirement for a portable class, other than the need to implement the IPortableObject interface, is that it must have a default constructor which will allow the POF deserializer to create an instance of the class during deserialization.

Here is an example of a user-defined portable class:

public class ContactInfo : IPortableObject
{
    private string name;
    private string street;
    private string city;
    private string state;
    private string zip;

    public ContactInfo()
    {}

    public ContactInfo(string name, string street, string city, string state, string zip)
    {
        Name   = name;
        Street = street;
        City   = city;
        State  = state;
        Zip    = zip;
    }

    public void ReadExternal(IPofReader reader)
    {
        Name   = reader.ReadString(0);
        Street = reader.ReadString(1);
        City   = reader.ReadString(2);
        State  = reader.ReadString(3);
        Zip    = reader.ReadString(4);
    }

    public void WriteExternal(IPofWriter writer)
    {
        writer.WriteString(0, Name);
        writer.WriteString(1, Street);
        writer.WriteString(2, City);
        writer.WriteString(3, State);
        writer.WriteString(4, Zip);
    }

    // property definitions ommitted for brevity
}

Creating a PortableObject Implementation (Java)

An implementation of the portable class in Java is very similar to the one in .NET from the example above:

public class ContactInfo implements PortableObject
    {
    private String m_sName;
    private String m_sStreet;
    private String m_sCity;
    private String m_sState;
    private String m_sZip;

    public ContactInfo()
        {
        }

    public ContactInfo(String sName, String sStreet, String sCity, String sState, String sZip)
        {
        setName(sName);
        setStreet(sStreet);
        setCity(sCity);
        setState(sState);
        setZip(sZip);
        }

    public void readExternal(PofReader reader)
            throws IOException
        {
        setName(reader.readString(0));
        setStreet(reader.readString(1));
        setCity(reader.readString(2));
        setState(reader.readString(3));
        setZip(reader.readString(4));
        }

    public void writeExternal(PofWriter writer)
            throws IOException
        {
        writer.writeString(0, getName());
        writer.writeString(1, getStreet());
        writer.writeString(2, getCity());
        writer.writeString(3, getState());
        writer.writeString(4, getZip());
        }

    // accessor methods omitted for brevity
}

Registering Custom Types on the .NET Client

Each POF user type is represented within the POF stream as an integer value. As such, POF requires an external mechanism that allows a user type to be mapped to its encoded type identifier (and visa versa). This mechanism uses an XML configuration file to store the mapping information. For example:

<?xml version="1.0"?>

<pof-config xmlns="http://schemas.tangosol.com/pof">
  <user-type-list>
    <!-- include all "standard" Coherence POF user types -->
    <include>assembly://Coherence/Tangosol.Config/coherence-pof-config.xml</include>

    <!-- include all application POF user types -->
    <user-type>
      <type-id>1001</type-id>
      <class-name>My.Example.ContactInfo, MyAssembly</class-name>
    </user-type>
    ...
  </user-type-list>
</pof-config>

There are few things to note:

  1. Type identifiers for your custom types should start from 1001 or higher, as the numbers below 1000 are reserved for internal use.
  2. You need not specify a fully-qualified type name within the class-name element. The type and assembly name is enough.

Once you have configured mappings between type identifiers and your custom types, you must configure Coherence for .NET to use them by adding a serializer element to your cache configuration descriptor. Assuming that user type mappings from the example above are saved into my-dotnet-pof-config.xml, you need to specify a serializer element as follows:

<remote-cache-scheme>
  <scheme-name>extend-direct</scheme-name>
  <service-name>ExtendTcpCacheService</service-name>
  <initiator-config>
    ...
    <serializer>
      <class-name>Tangosol.IO.Pof.ConfigurablePofContext, Coherence</class-name>
      <init-params>
        <init-param>
          <param-type>string</param-type>
          <param-value>my-dotnet-pof-config.xml</param-value>
        </init-param>
      </init-params>
    </serializer>
   </initiator-config>
</remote-cache-scheme>

The ConfigurablePofContext type will be used for the POF serializer if one is not explicitly specified. It uses a default configuration file ($AppRoot/coherence-pof-config.xml) if it exists, or a specific file determined by the contents of the pof-config element in the Coherence for .NET application configuration file. For example:

<?xml version="1.0"?>

<configuration>
  <configSections>
    <section name="coherence" type="Tangosol.Config.CoherenceConfigHandler, Coherence"/>
  </configSections>
  <coherence>
    <pof-config>my-dotnet-pof-config.xml</pof-config>
  </coherence>
</configuration>

See Configuring and Using the Coherence for .NET Client Library for additional details.

Registering Custom Types in the Cluster

Each Coherence node running the TCP/IP Coherence*Extend clustered service requires a similar POF configuration for the custom types in order to be able to send and receive objects of these types.

The cluster-side POF configuration file looks similar to the one we created on the client. The only difference is that instead of .NET class names, you must specify the fully qualified Java class names within the class-name element. For example, consider the following file called my-java-pof-config.xml:

<?xml version="1.0"?>
<!DOCTYPE pof-config SYSTEM "pof-config.dtd">

<pof-config>
  <user-type-list>
    <!-- include all "standard" Coherence POF user types -->
    <include>example-pof-config.xml</include>

    <!-- include all application POF user types -->
    <user-type>
      <type-id>1001</type-id>
      <class-name>com.mycompany.example.ContactInfo</class-name>
    </user-type>
    ...
  </user-type-list>
</pof-config>

Once your custom types have been added, you must configure the server to use your POF configuration when serializing objects:

<proxy-scheme>
  <service-name>ExtendTcpProxyService</service-name>
  <acceptor-config>
    ...
    <serializer>
      <class-name>com.tangosol.io.pof.ConfigurablePofContext</class-name>
      <init-params>
        <init-param>
          <param-type>string</param-type>
          <param-value>my-java-pof-config.xml</param-value>
        </init-param>
      </init-params>
    </serializer>
   </acceptor-config>
  ...
</proxy-scheme>

Evolvable Portable User Types

PIF-POF includes native support for both forwards- and backwards-compatibility of the serialized form of portable user types. In .NET, this is accomplished by making user types implement the IEvolvablePortableObject interface instead of the IPortableObject interface. The IEvolvablePortableObject interface is a marker interface that extends bot the IPortableObject and IEvolvable interfaces. The IEvolvable inteface adds three properties to support type versioning.

An IEvolvable class has an integer version identifier n, where n >= 0. When the contents and/or semantics of the serialized form of the IEvolvable class changes, the version identifier is increased. Two versions identifiers, n1 and n2, indicate the same version if n1 == n2; the version indicated by n2 is newer than the version indicated by n1 if n2 > n1.

The IEvolvable interface is designed to support the evolution of types by the addition of data. Removal of data cannot be safely accomplished as long as a previous version of the type exists that relies on that data. Modifications to the structure or semantics of data from previous versions likewise cannot be safely accomplished as long as a previous version of the type exists that relies on the previous structure or semantics of the data.

When an IEvolvable object is deserialized, it retains any unknown data that has been added to newer versions of the type, and the version identifier for that data format. When the IEvolvable object is subsequently serialized, it includes both that version identifier and the unknown future data.

When an IEvolvable object is deserialized from a data stream whose version identifier indicates an older version, it must default and/or calculate the values for any data fields and properties that have been added since that older version. When the IEvolvable object is subsequently serialized, it includes its own version identifier and all of its data. Note that there will be no unknown future data in this case; future data can only exist when the version of the data stream is newer than the version of the IEvolvable type.

The following example demonstrates how the ContactInfo .NET type can be modified to support class evolution:

public class ContactInfo : IEvolvablePortableObject
{
    private string name;
    private string street;
    private string city;
    private string state;
    private string zip;

    // IEvolvable members
    private int    version;
    private byte[] data;

    public ContactInfo()
    {}

    public ContactInfo(string name, string street, string city, string state, string zip)
    {
        Name   = name;
        Street = street;
        City   = city;
        State  = state;
        Zip    = zip;
    }

    public void ReadExternal(IPofReader reader)
    {
        Name   = reader.ReadString(0);
        Street = reader.ReadString(1);
        City   = reader.ReadString(2);
        State  = reader.ReadString(3);
        Zip    = reader.ReadString(4);
    }

    public void WriteExternal(IPofWriter writer)
    {
        writer.WriteString(0, Name);
        writer.WriteString(1, Street);
        writer.WriteString(2, City);
        writer.WriteString(3, State);
        writer.WriteString(4, Zip);
    }

    public int DataVersion
    {
        get { return version; }
        set { version = value; }
    }

    public byte[] FutureData
    {
        get { return data; }
        set { data = value; }
    }

    public int ImplVersion
    {
        get { return 0; }
    }

    // property definitions ommitted for brevity
}

Likewise, the ContactInfo Java type can also be modified to support class evolution by implementing the EvolvablePortableObject interface:

public class ContactInfo
        implements EvolvablePortableObject
    {
    private String m_sName;
    private String m_sStreet;
    private String m_sCity;
    private String m_sState;
    private String m_sZip;

    // Evolvable members
    private int    m_nVersion;
    private byte[] m_abData;

    public ContactInfo()
        {}

    public ContactInfo(String sName, String sStreet, String sCity,
            String sState, String sZip)
        {
        setName(sName);
        setStreet(sStreet);
        setCity(sCity);
        setState(sState);
        setZip(sZip);
        }

    public void readExternal(PofReader reader)
            throws IOException
        {
        setName(reader.readString(0));
        setStreet(reader.readString(1));
        setCity(reader.readString(2));
        setState(reader.readString(3));
        setZip(reader.readString(4));
        }

    public void writeExternal(PofWriter writer)
            throws IOException
        {
        writer.writeString(0, getName());
        writer.writeString(1, getStreet());
        writer.writeString(2, getCity());
        writer.writeString(3, getState());
        writer.writeString(4, getZip());
        }

    public int getDataVersion()
        {
        return m_nVersion;
        }

    public void setDataVersion(int nVersion)
        {
        m_nVersion = nVersion;
        }

    public Binary getFutureData()
        {
        return m_binData;
        }

    public void setFutureData(Binary binFuture)
        {
        m_binData = binFuture;
        }

    public int getImplVersion()
        {
        return 0;
        }

    // accessor methods omitted for brevity
    }

Making Types Portable Without Modification

In some cases, it may be undesirable or impossible to modify an existing user type to make it portable. In this case, you can externalize the portable serialization of a user type by creating an implementation of the IPofSerializer in .NET and/or an implementation of the PofSerializer interface in Java. For example, an implementation of the IPofSerializer interface for the ContactInfo type would look like:

public class ContactInfoSerializer : IPofSerializer
{
    public object Deserialize(IPofReader reader)
    {
        string name   = reader.ReadString(0);
        string street = reader.ReadString(1);
        string city   = reader.ReadString(2);
        string state  = reader.ReadString(3);
        string zip    = reader.ReadString(4);

        ContactInfo info = new ContactInfo(name, street, city, state, zip);

        info.DataVersion = reader.VersionId;
        info.FutureData  = reader.ReadRemainder();

        return info;
    }

    public void Serialize(IPofWriter writer, object o)
    {
        ContactInfo info = (ContactInfo) o;

        writer.VersionId = Math.Max(info.DataVersion, info.ImplVersion);
        writer.WriteString(0, info.Name);
        writer.WriteString(1, info.Street);
        writer.WriteString(2, info.City);
        writer.WriteString(3, info.State);
        writer.WriteString(4, info.Zip);
        writer.WriteRemainder(info.FutureData);
    }
}

An implementation of the PofSerializer interface for the ContactInfo Java type would look similar:

public class ContactInfoSerializer
        implements PofSerializer
    {
    public Object deserialize(PofReader in)
            throws IOException
        {
        String sName   = in.readString(0);
        String sStreet = in.readString(1);
        String sCity   = in.readString(2);
        String sState  = in.readString(3);
        String sZip    = in.readString(4);

        ContactInfo info = new ContactInfo(sName, sStreet, sCity, sState, sZip);

        info.setDataVersion(in.getVersionId());
        info.setFutureData(in.readRemainder());

        return info;
        }

    public void serialize(PofWriter out, Object o)
            throws IOException
        {
        ContactInfo info = (ContactInfo) o;

        out.setVersionId(Math.max(info.getDataVersion(), info.getImplVersion()));
        out.writeString(0, info.getName());
        out.writeString(1, info.getStreet());
        out.writeString(2, info.getCity());
        out.writeString(3, info.getState());
        out.writeString(4, info.getZip());
        out.writeRemainder(info.getFutureData());
        }
    }

To register the IPofSerializer implementation for the ContactInfo .NET type, specify the class name of the IPofSerializer within a serializer element under the user-type element for the ContactInfo user type in the POF configuration file:

<?xml version="1.0"?>

<pof-config xmlns="http://schemas.tangosol.com/pof">
  <user-type-list>
    <!-- include all "standard" Coherence POF user types -->
    <include>assembly://Coherence/Tangosol.Config/coherence-pof-config.xml</include>

    <!-- include all application POF user types -->
    <user-type>
      <type-id>1001</type-id>
      <class-name>My.Example.ContactInfo, MyAssembly</class-name>
      <serializer>
        <class-name>My.Example.ContactInfoSerializer, MyAssembly</class-name>
      </serializer>
    </user-type>
      ...
  </user-type-list>
</pof-config>

Similarly, you can register the PofSerializer implementation for the ContactInfo Java type like so:

<?xml version="1.0"?>
<!DOCTYPE pof-config SYSTEM "pof-config.dtd">

<pof-config>
  <user-type-list>
    <!-- include all "standard" Coherence POF user types -->
    <include>example-pof-config.xml</include>

    <!-- include all application POF user types -->
    <user-type>
      <type-id>1001</type-id>
      <class-name>com.mycompany.example.ContactInfo</class-name>
      <serializer>
        <class-name>com.mycompany.example.ContactInfoSerializer</class-name>
      </serializer>
    </user-type>
    ...
  </user-type-list>
</pof-config>

Configuring and Using the Coherence for .NET Client Library

To use the Coherence for .NET library in your .NET applications, you must add a reference to the Coherence.dll library in your project and create the necessary configuration files.

Creating a reference to the Coherence.dll:

  1. In your project go to Project->Add Reference... or right click on References in the Solution Explorer and choose Add Reference...
  2. In the Add Reference window that appears, choose the Browse tab and find the Coherence.dll library on your file system.
  3. Click OK.


Next, you must create the necessary configuration files and specify their paths in the application configuration settings. This is done by adding an application configuration file to your project (if one was not already created) and adding a Coherence for .NET configuration section (i.e. <coherence/>) to it.

<?xml version="1.0"?>

<configuration>
  <configSections>
    <section name="coherence" type="Tangosol.Config.CoherenceConfigHandler, Coherence"/>
  </configSections>
  <coherence>
    <cache-factory-config>my-coherence.xml</cache-factory-config>
    <cache-config>my-cache-config.xml</cache-config>
    <pof-config>my-pof-config.xml</pof-config>
  </coherence>
</configuration>

Elements within the Coherence for .NET configuration section are:

Having added these configuration files, your solution should look like:

CacheFactory

The CacheFactory is the entry point for Coherence for .NET client applications. The CacheFactory is a factory for INamedCache instances and provides various methods for logging. If not configured explicitly, it uses the default configuration file "coherence.xml" which is an assembly embedded resource. It is possible to override the default configuration file by adding a cache-factory-config element to the Coherence for .NET configuration section in the application configuration file and setting its value to the path of the desired configuration file.

<?xml version="1.0"?>

<configuration>
  <configSections>
    <section name="coherence" type="Tangosol.Config.CoherenceConfigHandler, Coherence"/>
  </configSections>
  <coherence>
    <cache-factory-config>my-coherence.xml</cache-factory-config>
    ...
  </coherence>
</configuration>

This file contains the configuration of two components exposed by the CacheFactory via static properties:

When you are finished using the CacheFactory (for example, during application shutdown), the CacheFactory should be shutdown via the Shutdown() method. This method terminates all services and the Logger instance.

IConfigurableCacheFactory

The IConfigurableCacheFactory implementation is specified by the contents of the configurable-cache-factory-config element:

Example:

<coherence>
  <configurable-cache-factory-config>
    <class-name>Tangosol.Net.DefaultConfigurableCacheFactory, Coherence</class-name>
    <init-params>
      <init-param>
        <param-type>string</param-type>
        <param-value>simple-cache-config.xml</param-value>
      </init-param>
    </init-params>
  </configurable-cache-factory-config>
</coherence>

If an IConfigurableCacheFactory implementation is not defined in the configuration, the a default implementation is used (DefaultConfigurableCacheFactory).

DefaultConfigurableCacheFactory

The DefaultConfigurableCacheFactory provides a facility to access caches declared in the cache configuration descriptor described earlier. The default configuration file used by the DefaultConfigurableCacheFactory is $AppRoot/coherence-cache-config.xml, where $AppRoot is the working directory (in the case of a Windows Forms application) or the root of the application (in the case of a Web application).
If you wish to specify another cache configuration descriptor file, you can do so by adding a cache-config element to the Coherence for .NET configuration section in the application configuration file with its value set to the path of the configuration file.

Example:

<?xml version="1.0"?>

<configuration>
  <configSections>
    <section name="coherence" type="Tangosol.Config.CoherenceConfigHandler, Coherence"/>
  </configSections>
  <coherence>
    <cache-config>my-cache-config.xml</cache-config>
    ...
  </coherence>
</configuration>

Logger

The Logger is configured using the logging-config element:

Example:

<coherence>
  <logging-config>
    <destination>log4net</destination>
    <severity-level>5</severity-level>
    <message-format>(thread={thread}): {text}</message-format>
    <character-limit>8192</character-limit>
  </logging-config>
</coherence>

The CacheFactory provides several static methods for retrieving and releasing INamedCache instances:

Methods used to log messages and exceptions are:

Logging levels are defined by the values of the CacheFactory.LogLevel enum values (in ascending order):

Common.Logging
Common.Logging is an open source library that allows you to plug in various popular open source logging libraries behind a well-defined set of interfaces. The libraries currently supported are Log4Net (versions 1.2.9 and 1.2.10) and NLog. Common.Logging is currently used by the Spring.NET framework and will likely be used in the future releases of IBatis.NET and NHibernate, so you might want to consider it if you are using one or more of these frameworks in combination with Coherence for .NET, as it will allow you to configure logging consistently throughout the application layers.

Coherence for .NET does not include the Common.Logging library. If you would like to use the "common-logger" Logger configuration, you must download the Common.Logging assembly and include a reference to it in your project. You can download the Common.Logging assemblies for both .NET 1.1 and 2.0 from the following location:

http://netcommon.sourceforge.net/

The Coherence for .NET Common.Logging Logger implementation was compiled against the signed release version of these assemblies.

INamedCache

The INamedCache interface extends IDictionary, so it can be manipulated in ways similar to a dictionary. Once obtained, INamedCache instances expose several properties:

The value for the specified key can be retreived via cache[key]. Similarly, a new value can be added, or an old value can be modified by setting this property to the new value: cache[key] = value.
The collection of cache entries can be accessed via GetEnumerator() which can be used to iterate over the mappings in the cache.

The INamedCache interface provides a number of methods used to manipulate the contents of the cache:

INamedCache interface also extends the following three interfaces: IQueryCache, IObservableCache, IInvocableCache.

IQueryCache

The IQueryCache interface exposes the ability to query a cache using various filters.

Additionally, the IQueryCache interface includes the ability to add and remove indexes. Indexes are used to correlate values stored in the cache to their corresponding keys and can dramatically increase the performance of the GetKeys and GetEntries methods.

For example:

The following code would perform an efficient query of the keys of all entries that have an age property value greater or equal to 55.

IValueExtractor extractor = new ReflectionExtractor("getAge");
cache.AddIndex(extractor, true, null);

ICollection keys = cache.GetKeys(new GreaterEqualsFilter(extractor, 55));

IObservableCache

IObservableCache interface enables an application to receive events when the contents of a cache changes. To register interest in change events, an application adds a Listener implementation to the cache that will receives events that include information about the event type (inserted, updated, deleted), the key of the modified entry, and the old and new values of the entry.

Listener registered using the filter-based method will receive all event types (inserted, updated, and deleted). To further filter the events, wrap the filter in a CacheEventFilter using a CacheEventMask enumeration value to specify which type of events should be monitored.

For example:

A filter that evaluates to true if an Employee object is inserted into a cache with an IsMarried property value set to true.

new CacheEventFilter(CacheEventMask.Inserted, new EqualsFilter("IsMarried", true));

A filter that evaluates to true if any object is removed from a cache.

new CacheEventFilter(CacheEventMask.Deleted);

A filter that evaluates to true if when an Employee object LastName property is changed from "Smith".

new CacheEventFilter(CacheEventMask.UpdatedLeft, new EqualsFilter("LastName", "Smith"));

IInvocableCache

An IInvocableCache is a cache against which both entry-targeted processing and aggregating operations can be invoked. The operations against the cache contents are executed by (and thus within the localized context of) a cache. This is particularly useful in a distributed environment, because it enables the processing to be moved to the location at which the entries-to-be-processed are being managed, thus providing efficiency by localization of processing.

Filters

The IQueryCache interface provides the ability to search for cache entries that meet a given set of criteria, expressed using a IFilter implementation.

All filters must implement the IFilter interface:

Coherence for .NET includes several IFilter implementations in the Tangosol.Util.Filter namespace.

The following code would retrieve the keys of all entries that have a value equal to 5.

EqualsFilter equalsFilter = new EqualsFilter(IdentityExtractor.Instance, 5);
ICollection  keys         = cache.GetKeys(equalsFilter);

The following code would retrieve all keys that have a value greater or equal to 55.

GreaterEqualsFilter greaterEquals = new GreaterEqualsFilter(IdentityExtractor.Instance, 55);
ICollection         keys          = cache.GetKeys(greaterEquals);

The following code would retrieve all cache entries that have a value that begins with "Belg".

LikeFilter  likeFilter = new LikeFilter(IdentityExtractor.Instance, "Belg%", '\\', true);
ICollection entries    = cache.GetEntries(likeFilter);

The following code would retrieve all cache entries that have a value that ends with "an" (case sensitive) or begins with "An" (case insensitive).

OrFilter    orFilter = new OrFilter(new LikeFilter(IdentityExtractor.Instance, "%an", '\\', false), new LikeFilter(IdentityExtractor.Instance, "An%", '\\', true));
ICollection entries  = cache.GetEntries(orFilter);

Extractors

Extractors are used to extract values from an object.

All extractors must implement the IValueExtractor interface:

Coherence for .NET includes the following extractors:

The following code would retrieve all cache entries with keys greater than 5:

IValueExtractor extractor = new KeyExtractor(IdentityExtractor.Instance);
IFilter         filter    = new GreaterFilter(extractor, 5);
ICollection     entries   = cache.GetEntries(filter);

The following code would retrieve all cache entries with values containing a City property equal to "city1":

IValueExtractor extractor = new ReflectionExtractor("City");
IFilter         filter    = new EqualsFilter(extractor, "city1");
ICollection     entries   = cache.GetEntries(filter);

Processors

A processor is an invocable agent that operates against the entry objects within a cache.

All processors must implement the IEntryProcessor interface:

Coherence for .NET includes several IEntryProcessor implementations in the Tangosol.Util.Processor namespace.

The following code demonstrates a conditional put. The value mapped to "key1" is set to 680 only if the current mapped value is greater than 600.

IFilter         greaterThen600 = new GreaterFilter(IdentityExtractor.Instance, 600);
IEntryProcessor processor      = new ConditionalPut(greaterThen600, 680);
cache.Invoke("key1", processor);

The following code uses the UpdaterProcessor to update the value of the Degree property on a Temperature object with key "BGD" to the new value 26.

cache.Insert("BGD", new Temperature(25, 'c', 12));
IValueUpdater   updater   = new ReflectionUpdater("setDegree");
IEntryProcessor processor = new UpdaterProcessor(updater, 26);
object          result    = cache.Invoke("BGD", processor);

Aggregators

An aggregator represents processing that can be directed to occur against some subset of the entries in an IInvocableCache, resulting in an aggregated result. Common examples of aggregation include functions such as minimum, maximum, sum and average. However, the concept of aggregation applies to any process that needs to evaluate a group of entries to come up with a single answer. Aggregation is explicitly capable of being run in parallel, for example in a distributed environment.

All aggregators must implement the IEntryAggregator interface:

Coherence for .NET includes several IEntryAggregator implementations in the Tangosol.Util.Aggregator namespace.

The following code returns the size of the cache:

IEntryAggregator aggregator = new Count();
object           result     = cache.Aggregate(cache.Keys, aggregator);

In following code returns an IDictionary with keys equal to the unique values in the cache and values equal to the number of instances of the corresponding value in the cache:

IEntryAggregator aggregator = GroupAggregator.CreateInstance(IdentityExtractor.Instance, new Count());
object           result     = cache.Aggregate(cache.Keys, aggregator);

Like cached value objects, all custom IFilter, IExtractor, IProcessor and IAggregator implementation classes must be correctly registered in the POF context of the .NET application and cluster-side node to which the client is connected. As such, corresponding Java implementations of the custom .NET types must be created, compiled, and deployed on the cluster-side node. Note that the actual execution of the these custom types is performed by the Java implementation and not the .NET implementation.

See Configuring a POF Context for additional details.

Launching a Coherence DefaultCacheServer Process

To start a DefaultCacheServer that uses the cluster-side Coherence cache configuration described earlier to allow Coherence for .NET clients to connect to the Coherence cluster via TCP/IP, you need to do the following:

For example (note that the following command is broken up into multiple lines here only for formatting purposes; this is a single command typed on one line):

java -cp coherence.jar -Dtangosol.coherence.cacheconfig=file://<path to the server-side cache configuration descriptor>
     com.tangosol.net.DefaultCacheServer

Attachments:
added_reference.png (image/png)
add_reference.png (image/png)