18 Building Integration Objects (.NET)

You can use Portable Object Format (POF) serialization when creating .NET clients.

This chapter includes the following sections:

Overview of Building Integration Objects (.NET)

Coherence caches are used to cache value objects. .NET clients require a platform-independent serialization format that allows both .NET clients and Coherence JVMs to properly serialize and deserialize value objects that are 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. See The PIF-POF Binary Format in Developing Applications with Oracle Coherence.

POF supports all common .NET types out-of-the-box. Custom .NET classes can also be serialized to a POF stream by completing the following steps:

  1. Create a .NET class that implements the IPortableObject interface. See Creating an IPortableObject Implementation.

  2. Create a matching Java class that implements the PortableObject interface in the same way. See Creating a PortableObject Implementation (Java).

  3. Register your custom .NET class on the client. See Registering Custom Types on the .NET Client.

  4. Register your custom Java class on each of the servers running the Coherence*Extend clustered service. See Registering Custom Types in the Cluster.

After 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 can retrieve, manipulate, and store these types from a Coherence or Coherence*Extend JVM using the matching Java classes.

Creating an IPortableObject Implementation

Each class that implements IPortableObject can self-serialize and deserialize 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 requirement to implement the IPortableObject interface, is that it must have a default constructor which allows the POF deserializer to create an instance of the class during deserialization.

Example 18-1 illustrates a user-defined portable class:

Example 18-1 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
}

Implementing a Java Version of a .NET Object

A .NET object must have a parallel Java implementation on the cluster if direct access to the deserialized object is required.

The use of POF allows key and value objects to be stored within the cluster without the need for parallel Java implementations. This is ideal for performing basic get and put based operations. In addition, the PofExtractor and PofUpdater APIs add flexibility in working with non-primitive types in Coherence. For many extend client cases, a corresponding Java classes in the grid is not required. Because POF extractors and POF updaters can navigate the binary, the entire key and value does not have to be deserialized into object form. This implies that indexing can be achieved by simply using POF extractors to pull a value to index on.

When to Include a Parallel Java Implementation

A parallel Java implementation is required whenever the Java-based cache servers must directly interact with a data object rather then simply holding onto a serialized representation of it. For example, a Java class is still required when using a cache store. In this case, the deserialized version of the key and value is passed to the cache store to write to the back end. In addition, queries, filters, entry processors, and aggregators require a Java implementation if direct access to the object is desired.

If a Java implementation is required, then the implementation must be located on the cache servers. The approach to making the Java version serializable over POF is demonstrated in Creating a PortableObject Implementation (Java).

Deferring the Key Association Check

Key classes do not require a cluster-side Java implementation even if the key class specifies data affinity using the IKeyAssociation interface. Key classes are checked on the client side and a decorated binary is created and used by the cluster. However, existing client implementations that do rely on a Java key class for key association must set the defer-key-association-check parameter in order to force the use of the Java key class. Existing client applications that use key association but want to leverage client-side key binaries, must port the getAssociatedKey() implementation from the existing Java class to the corresponding client class (see IKeyAssociation.AssociatedKey.

To force key association processing to be done on the cluster side instead of by the extend client, set the <defer-key-association-check> element, within a <remote-cache-scheme> element, in the client-side cache configuration to true. For example:

<remote-cache-scheme>
   ...
   <defer-key-association-check>true</defer-key-association-check>
</remote-cache-scheme>

Note:

If the parameter is set to true, a Java key class implementation must be found on the cluster even if key association is no being used.

This section includes the following topic:

Creating a PortableObject Implementation (Java)

An implementation of the portable class in Java is very similar to the one in .NET. Example 18-2 illustrates the Java version of the .NET class in Example 18-1.

Example 18-2 A User-Defined Class in Java

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 the opposite is true as well). The POF XML configuration file maps user types to a type identifier. See POF User Type Configuration Elements in Developing Applications with Oracle Coherence.

The following example demonstrates a 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>
      </user-type>
   </user-type-list>
</pof-config>

There are few things to note:

  • Type identifiers for your custom types should start from 1001 or higher, as the numbers below 1000 are reserved for internal use. As shown in the above example, the <user-type-list> includes the coherence-pof-config.xml file. This is where Coherence specific user types are defined and should be included in all of your POF configuration files

  • You need not specify a fully qualified type name within the class-name element. The type and assembly name is enough.

After 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. The following examples assumes that the user type mappings are saved in the my-dotnet-pof-config.xml file:

<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>

If a serializer is not explicitly specified, the ConfigurablePofContext type is used for the POF serializer and uses a default configuration file called pof-config.xml. The Coherence .Net application looks for the default POF configuration file in both the folder where the application is deployed and, for Web applications, in the root of the Web application. If a POF configuration file is not found, it tries to located the file 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>

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 to be able to send and receive objects of these types. The cluster-side POF configuration file looks similar to the file 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.

The following illustrates a sample cluster-side POF configuration file called my-java-pof-config.xml:

<?xml version="1.0"?>

<pof-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="http://xmlns.oracle.com/coherence/coherence-pof-config"
   xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-pof-config
   coherence-pof-config.xsd">
   <user-type-list>
   <!-- include all "standard" Coherence POF user types -->
      <include>coherence-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>

After 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

POF includes native support for both forward- and backward-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 both the IPortableObject and IEvolvable interfaces. The IEvolvable interface adds three properties to support type versioning.An IEvolvable class has an integer version identifier n, where n >= 0. When the contents, or semantics, or both 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 if 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 if 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 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 is 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.

Example 18-3 demonstrates how the ContactInfo .NET type can be modified to support class evolution:

Example 18-3 Modifying a Class 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:

Example 18-4 Modifying a Java Type Class to Support Class Evolution

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. You can externalize the portable serialization of a user type by creating an IPofSerializer implementation.

Example 18-5 illustrates, an implementation of the IPofSerializer interface for the ContactInfo type.

Example 18-5 An Implementation of IPofSerializer for the .NET Type

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:

Example 18-6 An Implementation of PofSerializer for the Java Type Class

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. For example:

<?xml version="1.0"?>

<pof-config xmlns="http://schemas.tangosol.com/pof"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://schemas.tangosol.com/pof
   assembly://Coherence/Tangosol.Config/pof-config.xsd">
   <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:

<?xml version="1.0"?>

<pof-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="http://xmlns.oracle.com/coherence/coherence-pof-config"
   xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-pof-config
   coherence-pof-config.xsd">
   <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>

Using POF Object References

POF supports the use of object identities and references for objects that occur more than once in a POF stream. Objects are labeled with an identity and subsequent instances of a labeled object within the same POF stream are referenced by its identity.

Using references avoids encoding the same object multiple times and helps reduce the data size. References are typically used when a large number of sizeable objects are created multiple times or when objects use nested or circular data structures. However, for applications that contain large amounts of data but only few repeats, the use of object references provides minimal benefits due to the overhead incurred in keeping track of object identities and references.

The use of object identity and references has the following limitations:

  • Object references are only supported for user defined object types.

  • Object references are not supported for IEvolvable objects.

  • Object references are not supported for keys.

  • Objects that have been written out with a POF context that does not support references cannot be read by a POF context that supports references. The opposite is also true.

  • POF objects that use object identity and references cannot be queried using POF extractors. Instead, use the IValueExtractor API to query object values or disable object references.

  • The use of the IPofNavigator and IPofValue API has the following restrictions when using object references:

    • Only read operations are allowed. Write operations result in an UnsupportedOperationException.

    • User objects can be accessed in non-uniform collections but not in uniform collections.

    • For read operations, if an object appears in the data stream multiple times, then the object must be read where it first appears before it can be read in the subsequent part of the data. Otherwise, an IOException: missing identity: <ID> may be thrown. For example, if there are 3 lists that all contain the same person object, p. The p object must be read in the first list before it can be read in the second or third list.

This section includes the following topics:

Enabling POF Object References

Object references are not enabled by default and must be enabled either within a pof-config.xml configuration file or programmatically when using the SimplePofContext class.

To enable object references in the POF configuration file, include the <enable-references> element, within the <pof-config> element, and set the value to true. For example:

<?xml version="1.0"?>

<pof-config xmlns="http://schemas.tangosol.com/pof"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://schemas.tangosol.com/pof
   assembly://Coherence/Tangosol.Config/pof-config.xsd">
   ...
   <enable-references>true</enable-references>
</pof-config>

To enable object references when using the SimplePofContext class, call the setReferenceEnabled method and set it to true. For example:

SimplePofContext ctx = new SimplePofContext();
ctx.IsReferenceEnabled = true;

Registering POF Object Identities for Circular and Nested Objects

Circular or nested objects must manually register an identity when creating the object. Otherwise, a child that references the parent will not find the identity of the parent in the reference map. Object identities can be registered from a serializer during the deserialization routine using the Tangosol.IO.Pof.IPofReader.RegisterIdentity method.

The following examples demonstrate two objects (Customer and Product) that contain a circular reference and a serializer implementation that registers an identity on the Customer object.

The Customer object is defined as follows:

public class Customer
   {
      String m_name;
      Product m_product;
 
   public Customer(String name)
      {
      m_name = name;
      }
 
   public Customer(String name, Product product)
      {
      m_name = name;
      m_product = product;
      }
 
   public String getName()
      {
      return m_name;
      }
 
   public Product getProduct()
      {
      return m_product;
      }
 
   public void setProduct(Product product)
      {
         m_product = product;
      }
   }

The Product object is defined as follows:

public class Product
   {
      private Customer m_customer;
 
   public Product(Customer customer)
      {
      m_customer = customer;
      }
 
   public Customer getCustomer()
      {
      return m_customer;
      }
   }

The serializer implementation registers an identity during deserialization and is defined as follows:

public class CustomerSerializer : IPofSerializer
   {
   public void Serialize(IPofWriter pofWriter, object o)
      {
      var c = (Customer) o;
      pofWriter.WriteString(0, c.getName());
      pofWriter.WriteObject(1, c.getProduct());
      pofWriter.WriteRemainder(null);
   }
 
   public object Deserialize(IPofReader pofReader)
      {
      String name = pofReader.ReadString(0);
      var customer = new Customer(name);
 
      pofReader.RegisterIdentity(customer);
      customer.setProduct((Product) pofReader.ReadObject(1));
      pofReader.ReadRemainder();
      return customer;
      }
   }

Using POF Annotations to Serialize Objects

POF annotations provide an automated way to implement the serialization and deserialization routines for an object. POF annotations are serialized and deserialized using the PofAnnotationSerializer class which is an implementation of the IPofSerializer interface. Annotations offer an alternative to using the IPortableObject and IPofSerializer interfaces and reduce the amount of time and code that is required to make objects serializable.

This section includes the following topics:

Annotating Objects for POF Serialization

Two annotations are available to indicate that a class and its properties are POF serializable:

  • [Portable] – Marks the class as POF serializable. The annotation is only permitted at the class level and has no members.

  • [PortableProperty] – Marks a property, accessor, or member variable as a POF serialized property. Annotated methods must conform to accessor notation (Get, Set, Is). Members can be used to specify POF indexes as well as custom codecs that are executed before or after serialization or deserialization. Index values may be omitted and automatically assigned. If a custom codec is not entered, the default codec is used.

The following example demonstrates annotating a class, property, and member variable. In addition PortableProperty indexes are explicitly specified.

[Portable]
public class Person
{
   [PortableProperty(0)]
   public string GetFirstName()
   {
      return m_firstName;
   }

   private String m_firstName;

   [PortableProperty(1)]
   public string LastName;
   {
      get; set;
   }

   [PortableProperty(2)]
   private int m_age;
}

Registering POF Annotated Objects

POF annotated objects must be registered in a pof-config.xml file within a <user-type> element. See POF User Type Configuration Elements in Developing Applications with Oracle Coherence. POF annotated objects use the PofAnnotationSerializer serializer if an object does not implement IPortableObject and is annotated as Portable; however, the serializer is automatically assumed if an object is annotated and does not need to be included in the user type definition. The following example registers a user type for an annotated Person object:

<?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
      <!-- User types must be above 1000 -->
      <user-type>
         <type-id>1001</type-id>
         <class-name>My.Examples.Person, MyAssembly</class-name>
      </user-type>
   </user-type-list>
</pof-config>

Enabling Automatic Indexing

POF annotations support automatic indexing which alleviates the need to explicitly assign and manage index values. Omit the index value when defining the [PortableProperty] annotation. Index allocation is determined by the property name. Any property that does assign an explicit index value is not assigned an automatic index value. The following table demonstrates the ordering semantics of the automatic index algorithm. Notice that automatic indexing maintains explicitly defined indexes (as shown for property c) and assigns an index value if an index is omitted.

Name Explicit Index Determined Index

c

1

1

a

omitted

0

b

omitted

2

Note:

Automatic indexing does not currently support evolvable classes.

To enable automatic indexing, the PofAnnotationSerializer serializer class must be explicitly defined when registering the object as a user type in the POF configuration file. The autoIndex boolean parameter in the constructor enables automatic indexing and must be set to true. For example:

<user-type>
   <type-id>1001</type-id>
   <class-name>Examples.Person</class-name>
   <serializer>
      <class-name>Tangosol.IO.Pof.PofAnnotationSerializer, Coherence</class-name>
         <init-params>
         <init-param>
            <param-type>int</param-type>
            <param-value>{type-id}</param-value>
         </init-param>
         <init-param>
            <param-type>class</param-type>
            <param-value>{class}</param-value>
         </init-param>
         <init-param>
            <param-type>bool</param-type>
            <param-value>true</param-value>
         </init-param>
      </init-params>
   </serializer>
</user-type>

Providing a Custom Codec

Codecs allow code to be executed before or after serialization or deserialization. A codec defines how to encode and decode a portable property using the IPofWriter and IPofReader interfaces. Codecs are typically used for concrete implementations that could get lost when being deserialized or to explicitly call a specific method on the IPofWriter interface before serializing an object.

To create a codec, create a class that implements the ICodec interface. The following example demonstrates a codec that defines the concrete implementation of a linked list type:

public class LinkedListCodec<T> : ICodec
{
   public object Decode(IPofReader reader, int index)
      {
         return reader.ReadCollection(index, (ICollection)new LinkedList<T>());
      }

   public void Encode(IPofWriter writer, int index, object value)
      {
         writer.WriteCollection(index, (ICollection)value);
      }
}

To assign a codec to a property, enter the codec as a member of the [PortableProperty] attribute. If a codec is not specified, a default codec (DefaultCodec) is used. The following example demonstrates assigning the above LinkedListCodec codec:

[PortableProperty(typeof(LinkedListCodec<string>))]