10 Building Integration Objects (C++)

This chapter provides instructions for using Portable Object Format (POF) serialization when creating C++ clients.

Note:

This document assumes familiarity with the Coherence C++ Object Model, including advanced concepts such as specification-based class definitions. For more information on these topics, see Chapter 8, "Using the Coherence C++ Object Model."

This chapter includes the following sections:

10.1 Overview of Building Integration Objects (C++)

Enabling C++ clients to successfully store C++ based objects within a Coherence cluster relies on a platform-independent serialization format known as POF (Portable Object Format). 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. The stream can then be deserialized in an alternate language using a similar POF-based class definition. For more information on the POF binary stream, see Developing Applications with Oracle Coherence

While the Coherence C++ API includes several POF serializable classes, custom data types require serialization support as described in this chapter.

10.2 POF Intrinsics

The following types are internally supported by POF, and do not require special handling by the user:

  • String

  • Integer16 .. Integer64

  • Float32, Float64

  • Array<> of primitives

  • ObjectArray

  • Boolean

  • Octet

  • Character16

Additionally, automatic POF serialization is provided for classes implementing these common interfaces:

  • Map

  • Collection

  • Exception

10.3 Serialization Options

While the Coherence C++ API offers a single serialization format (POF), it offers a variety of APIs for making a class serializable. Ultimately whichever approach is used, the same binary POF format is produced. The following approaches are available for making a class serializable:

Table 10-1 lists some requirements and limitations of each approach.

Table 10-1 Requirements and Limitations of Serialization Options

Approach Coherence headers in data-object Requires derivation from Object Supports const data-members External serialization routine Requires zero-arg constructor

Managed<T>

No

No

Yes

Yes

Yes

PortableObject

Yes

Yes

No

No

Yes

PofSerializer

Yes

Yes

Yes

Yes

No


All of these approaches share certain similarities:

  • Serialization routines that allow the data items to be encoded to POF must be implemented.

  • The data object's fields are identified by using numeric indexes.

  • The data object class and serialization mechanism must be registered with Coherence.

  • Data objects used as cache keys must support equality comparisons and hashing.

10.3.1 Managed<T> (Free-Function Serialization)

For most pre-existing data object classes, the use of Managed<T> offers the easiest means of integrating with Coherence for C++.

For a non-managed class to be compatible with Managed<T> it must have the following characteristics:

  • zero parameter constructor (public or protected): CustomType::CustomType()

  • copy constructor (public or protected): CustomType::CustomType(const CustomType&)

  • equality comparison operator: bool operator==(const CustomType&, const CustomType&)

  • std::ostream output function: std::ostream& operator<<(std::ostream&, const CustomType&)

  • hash function: size_t hash_value(const CustomType&)

The following example presents a simple Address class, which has no direct knowledge of Coherence, but is suitable for use with the Managed<T> template.

Note:

In the interest of brevity, example class definitions are in-lined within the declaration.

Example 10-1 A Non-Managed Class

#include <iostream>
#include <string>
using namespace std;

class Address
  {
  public:
    Address(const std::string& sCity, const std::string& sState, int nZip)
       : m_sCity(sCity), m_sState(sState), m_nZip(nZip) {}

    Address(const Address& that) // required by Managed<T>
       : m_sCity(that.m_sCity), m_sState(that.m_sState), m_nZip(that.m_nZip) {}

  protected:
    Address() // required by Managed<T>
      : m_nZip(0) {}

  public:
    std::string  getCity()  const {return m_sCity;}
    std::string  getState() const {return m_sState;}
    int          getZip()   const {return m_nZip;}

  private:
    const std::string m_sCity;
    const std::string m_sState;
    const int         m_nZip;
  };

bool operator==(const Address& addra, const Address& addrb) // required by Managed<T>
  {
  return addra.getZip()   == addrb.getZip() &&
         addra.getState() == addrb.getState() &&
         addra.getCity()  == addrb.getCity();
  }

std::ostream& operator<<(std::ostream& out, const Address& addr) // required by Managed<T>
  {
  out << addr.getCity() << ", " << addr.getState() << "  " << addr.getZip();
  return out;
  }

size_t hash_value(const Address& addr) // required by Managed<T>
  {
  return (size_t) addr.getZip();
  }

When combined with Managed<T>, this simple class definition becomes a true "managed object", and is usable by the Coherence C++ API. This definition has yet to address serialization. Serialization support is added Example 10-2:

Example 10-2 Managed Class using Serialization

#include "coherence/io/pof/SystemPofContext.hpp"

#include "Address.hpp"

using namespace coherence::io::pof;

COH_REGISTER_MANAGED_CLASS(1234, Address); // type ID registration—this must
                                           // appear in the .cpp not the .hpp 

template<> void serialize<Address>(PofWriter::Handle hOut, const Address& addr)
  {
  hOut->writeString(0, addr.getCity());
  hOut->writeString(1, addr.getState());
  hOut->writeInt32 (2, addr.getZip());
  }

template<> Address deserialize<Address>(PofReader::Handle hIn)
  {
  std::string sCity  = hIn->readString(0);
  std::string sState = hIn->readString(1);
  int         nZip   = hIn->readInt32 (2);
  return Address(sCity, sState, nZip);
  }

Note:

The serialization routines must have knowledge of Coherence. However, they are not required as part of the class definition file. They can be placed in an independent source file, and if they are linked into the final application, they take effect.

With the above pieces in place, Example 10-3 illustrates instances of the Address class wrapped by using Managed<T> as Managed<Address>, and supplied to the Coherence APIs:

Example 10-3 Instances of a Class Wrapped with Managed<T>

// construct the non-managed version as usual
Address office("Redwood Shores", "CA", 94065);

// the managed version can be initialized from the non-managed version
// the result is a new object, which does not reference the original
Managed<Address>::View vOffice = Managed<Address>::create(office);
String::View           vKey    = "Oracle";

// the managed version is suitable for use with caches
hCache->put(vKey, vAddr);
vOffice = cast<Managed<Address>::View>(hCache->get(vKey));

// the non-managed class's public methods/fields remain accessible
assert(vOffice->getCity()  == office.getCity());
assert(vOffice->getState() == office.getState());
assert(vOffice->getZip()   == office.getZip());

// conversion back to the non-managed type may be performed using the
// non-managed class's copy constructor.
Address officeOut = *vOffice;

10.3.2 PortableObject (Self-Serialization)

The PortableObject interface is similar in concept to java.io.Externalizable, which allows an object to control how it is serialized. Any class which extends from coherence::lang::Object is free to implement the coherence::io::pof::PortableObject interface to add serialization support. Note that the class must extend from Object, which then dictates its life cycle.

In Example 10-4, the above Address example can be rewritten as a managed class, and implement the PortableObject interface, which fully embraces the Coherence object model as part of the definition of the class. For example, using coherence::lang::String rather then std::string for data members.

Example 10-4 A Managed Class that Implements PortableObject

#include "coherence/lang.ns"

#include "coherence/io/pof/PofReader.hpp"
#include "coherence/io/pof/PofWriter.hpp"
#include "coherence/io/pof/PortableObject.hpp"

#include "coherence/io/pof/SystemPofContext.hpp"

using namespace coherence::lang;

using coherence::io::pof::PofReader;
using coherence::io::pof::PofWriter;
using coherence::io::pof::PortableObject;

class Address
  : public cloneable_spec<Address,
      extends<Object>,
      implements<PortableObject> >
  {
  friend class factory<Address>;

  protected: // constructors
    Address(String::View vsCity, String::View vsState, int32_t nZip)
       : m_vsCity(self(), vsCity), m_vsState(self(), vsState), m_nZip(nZip) {}

    Address(const Address& that)
       : super(that), m_vsCity(self(), that.m_vsCity), m_vsState(self(), 
       that.m_vsState), m_nZip(that.m_nZip) {}

    Address() // required by PortableObject
       : m_vsCity(self()),
         m_vsState(self()),
         m_nZip(0) {}

  public: // Address interface   
    virtual String::View  getCity()  const {return m_vsCity;}
    virtual String::View  getState() const {return m_vsState;}
    virtual int32_t       getZip()   const {return m_nZip;}

  public: // PortableObject interface    virtual void writeExternal(PofWriter::Handle hOut) const
      {
      hOut->writeString(0, getCity());
      hOut->writeString(1, getState());
      hOut->writeInt32 (2, getZip());
      }

    virtual void readExternal(PofReader::Handle hIn)
      {
       initialize(m_vsCity, hIn->readString(0));
       initialize(m_vsState, hIn->readString(1));
       m_nZip    = hIn->readInt32 (2);
      }

  public: // Objectinterface    virtual bool equals(Object::View that) const
      {
      if (instanceof<Address::View>(that))
        {
        Address::View vThat = cast<Address::View>(that);

        return getZip() == vThat->getZip() &&
               Object::equals(getState(), vThat->getState()) &&
               Object::equals(getCity(), vThat->getCity());
        }

      return false;
      }

    virtual size32_t hashCode() const
      {
      return (size32_t) m_nZip;
      }

    virtual void toStream(std::ostream& out) const
      {
      out << getCity() << ", " << getState() << "  " << getZip();
      }

  private:
     FinalView<String> m_vsCity;
     FinalView<String> m_vsState;
     int32_t     m_nZip;
  };
COH_REGISTER_PORTABLE_CLASS(1234, Address); // type ID registration—this must
                                            // appear in the .cpp not the .hpp 

Example 10-5 illustrates a managed variant of the Address that does not require the use of the Managed<T> adapter and can be used directly with the Coherence API:

Example 10-5 A Managed Class without Managed<T>

Address::View vAddr = Address::create("Redwood Shores", "CA", 94065);
String::View  vKey  = "Oracle";

hCache->put(vKey, vAddr);
Address::View vOffice = cast<Address::View>(hCache->get(vKey));

Serialization by using PortableObject is a good choice when the application has decided to make use of the Coherence object model for representing its data objects. One drawback to PortableObject is that it does not easily support const data members, as the readExternal method is called after construction, and must assign these values.

10.3.3 PofSerializer (External Serialization)

The third serialization option is also the lowest level one. PofSerializers are classes that provide the serialization logic for other classes. For example, an AddressSerializer is written which can serialize a non-PortableObject version of the above managed Address class. Under the covers the prior two approaches were delegating through PofSerializers, they were just being created automatically rather then explicitly. Typically, it is not necessary to use this approach, as either the Managed<T> or PortableObject approaches suffice. This approach is primarily of interest when you have a managed object with const data members. Consider Example 10-6, a non-PortableObject version of a managed Address.

Example 10-6 A non-PortableObject Version of a Managed Class

#include "coherence/lang.ns"

using namespace coherence::lang;

class Address
  : public cloneable_spec<Address> // extends<Object> is implied
  {
  friend class factory<Address>;

  protected: // constructors
    Address(String::View vsCity, String::View vsState, int32_t nZip)
       : m_vsCity(self(), vsCity), m_vsState(self(), vsState), m_nZip(nZip) {}


    Address(const Address& that)
       : super(that), m_vsCity(self(), that.getCity()), m_vsState(self(),
         that.getState()), m_nZip(that.getZip()) {}

  public: // Address interface    
    virtual String::View  getCity()  const {return m_vsCity;}
    virtual String::View  getState() const {return m_vsState;}
    virtual int32_t getZip() const {return m_nZip;}

  public: // Objectinterface    
       virtual bool equals(Object::View that) const
      {
      if (instanceof<Address::View>(that))
        {
        Address::View vThat = cast<Address::View>(that);

        return getZip() == vThat->getZip() &&
               Object::equals(getState(), vThat->getState()) &&
               Object::equals(getCity(), vThat->getCity());
        }

      return false;
      }

    virtual size32_t hashCode() const
      {
      return (size32_t) m_nZip;
      }

    virtual void toStream(std::ostream& out) const
      {
      out << getCity() << ", " << getState() << "  " << getZip();
      }

  private:
    const MemberView<String> m_vsCity;
    const MemberView<String> m_vsState;
    const int32_t      m_nZip;
  };

Note that this version uses const data members, which makes it not well-suited for PortableObject. Example 10-7 illustrates an external class, AddressSerializer, which is registered as being responsible for serialization of Address instances.

Example 10-7 An External Class Responsible for Serialization

#include "coherence/lang.ns"

#include "coherence/io/pof/PofReader.hpp"
#include "coherence/io/pof/PofWriter.hpp"
#include "coherence/io/pof/PortableObject.hpp"
#include "coherence/io/pof/PofSerializer.hpp"
#include "coherence/io/pof/SystemPofContext.hpp"

#include "Address.hpp"

using namespace coherence::lang;

using coherence::io::pof::PofReader;
using coherence::io::pof::PofWriter;
using coherence::io::pof::PofSerializer;

class AddressSerializer
  : public class_spec<AddressSerializer,
      extends<Object>,
      implements<PofSerializer> >
  {
  friend class factory<AddressSerializer>;

  protected:
    AddressSerializer();

  public: // PofSerializer interface    virtual void serialize(PofWriter::Handle hOut, Object::View v) const
        {
        Address::View vAddr = cast<Address::View>(v);
        hOut->writeString(0, vAddr->getCity());
        hOut->writeString(1, vAddr->getState());
        hOut->writeInt32 (2, vAddr->getZip());
        hOut->writeRemainder(NULL);
        }

    virtual Object::Holder deserialize(PofReader::Handle hIn) const
      {
      String::View vsCity  = hIn->readString(0);
      String::View vsState = hIn->readString(1);
      int32_t      nZip    = hIn->readInt32 (2);
      hIn->readRemainder();

      return Address::create(vsCity, vsState, nZip);
      }
  };
COH_REGISTER_POF_SERIALIZER(1234, TypedBarrenClass<Address>::create(),AddressSerializer::create()); // This must appear in the .cpp not the .hpp

Usage of the Address remains unchanged:

Address::View vAddr = Address::create("Redwood Shores", "CA", 94065);
String::View  vKey  = "Oracle";

hCache->put(vKey, vAddr);
Address::View vOffice = cast<Address::View>(hCache->get(vKey));

10.4 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 Evolvable 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 ValueExtractor API to query object values or disable object references.

  • The use of the PofNavigator and PofValue 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.

The following topics are included in this section:

10.4.1 Enabling POF Object References

Object references are not enabled by default and must be enabled using setReferenceEnabled when creating a POF context. For example:

SystemPofContext::Handle hCtx = SystemPofContext::getInstance();
hCtx->setReferenceEnabled(true);

10.4.2 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 PofReader.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:

class Customer
   : public class_spec<Customer,
      extends<Object> >
   {
   friend class factory<Customer>;
 
   protected:
      Customer()
         : m_vsName(self(), String::null_string),
         m_vProduct(self(), NULL)
         {
         }
 
         Customer(String::View vsName)
            : m_vsName(self(), vsName),
            m_vProduct(self(), NULL)
            {
         }
 
         Customer(String::View vsName, Product::View vProduct)
         : m_vsName(self(), vsName),
         m_vProduct(self(), vProduct)
         {
         }
 
   public:
      String::View getName() const
         {
         return m_vsName;
         }
 
      void setName(String::View vsName)
         {
         m_vsName = vsName;
         }
 
      Product::View getProduct() const
         {
         return m_vProduct;
         }
 
      void setProduct(Product::View vProduct)
         {
         m_vProduct = vProduct;
         }
 
   private:
      MemberView<String> m_vsName;
      MemberView<Product> m_vProduct;
   };

The Product object is defined as follows:

class Product
   : public class_spec<Product,
      extends<Object> >
   {
   friend class factory<Product>;
 
   protected:
      Product()
         : m_vCustomer(self(), NULL)
         {
         }
 
      Product(Customer::View vCustomer)
         : m_vCustomer(self(), vCustomer)
         {
         }
 
   public:
      Customer::View getCustomer() const
         {
         return m_vCustomer;
         }
 
      void setCustomer(Customer::View vCustomer)
         {
         m_vCustomer= vCustomer;
         }
 
   private:
         MemberView<Customer> m_vCustomer;
   };

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

class CustomerSerializer
   : public class_spec<CustomerSerializer,
      extends<Object>,
      implements<PofSerializer> >
   {
   friend class factory<CustomerSerializer>;
 
   public:
      void serialize(PofWriter::Handle hOut, Object::View v) const
         {
         Customer::View vCustomer = cast<Customer::View>(v);
         hOut->writeString(0, vCustomer->getName());
         hOut->writeObject(1, vCustomer->getProduct());
         hOut->writeRemainder(NULL);
         }

      Object::Holder deserialize(PofReader::Handle hIn) const
         {
         String::View vsName = cast<String::View>(hIn->readString(0));
         Customer::Holder ohCustomer = Customer::create(vsName);
 
         hIn->registerIdentity(ohCustomer);
         ohCustomer->setProduct(cast<Product::View>(hIn->readObject(1)));
         hIn->readRemainder();
         return ohCustomer;
         }
   };

10.5 Registering Custom C++ Types

In addition to being made serializable, each class must also be associated with numeric type IDs. These IDs are well-known across the cluster. Within the cluster, the ID-to-class mapping is configured by using POF user type configuration elements; within C++, the mapping is embedded within the class definition in the form of an ID registration, which is placed within the class's .cpp source file.

The registration technique differs slightly with each serialization approach:

  • COH_REGISTER_MANAGED_CLASS(ID, TYPE)—for use with Managed<T>

  • COH_REGISTER_PORTABLE_CLASS(ID, TYPE)—for use with PortableObject

  • COH_REGISTER_POF_SERIALIZER(ID, CLASS, SERIALIZER)—for use with PofSerializer

Examples of these registrations can be found in above examples.

Note:

Registrations must appear only in the implementation (.cpp) files. A POF configuration file is only needed on the nodes where objects are serialized and deserialize.

10.6 Implementing a Java Version of a C++ Object

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 similar to the above examples, see PortableObject and PofSerializer for details. These APIs are compatible with all three of the C++ approaches.

Deferring the Key Association Check

Key classes do not require a cluster-side Java implementation even if the key class specifies data affinity using KeyAssociation. 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.

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.

10.7 Understanding Serialization Performance

Both Managed<T> and PortableObject behind the scenes use a PofSerializer to perform serialization. Each of these approaches also adds some of its own overhead, for instance the Managed<T> approach involves the creation of a temporary version of non-managed form of the data object during deserialization. For PortableObject, the lack of support for const data members can have a cost as it avoids optimizations which would have been allowed for const data members. Overall the performance differences may be negligible, but if seeking to achieve the maximum possible performance, direct utilization of PofSerializer may be worth consideration.

10.8 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 PofSerializer interface. Annotations offer an alternative to using the Managed<T> adapter, PortableObject interface, and PofSerializer interface and reduce the amount of time and code that is required to make objects serializable.

The following topics are included in this section:

10.8.1 Annotating Objects for POF Serialization

Two annotations are available to indicate that a class and its methods 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 method accessor 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 and method and also explicitly assigns property index values. Note that the class must be registered with the system class loader COH_REGISTER_CLASS.

class Person
   : public class_spec<Person>
   {
   friend class factory<Person>;

   Public:
      String::View getFirstName() const
         {
         return m_vsFirstName;
         }

      void setFirstName(String::View vsFirstName)
         {
         m_vsFirstName = vsFirstName;
         }

   private: String m_firstName;
      MemberView<String> m_vsFirstName;
      MemberView<String> m_vsLastName;
      int32_t            m_nAge;

   public:
      static const int32_t FIRST_NAME = 0;
      static const int32_t LAST_NAME  = 1;
      static const int32_t AGE        = 2;
   };

COH_REGISTER_CLASS(TypedClass<Person>::create()
   ->annotate(Portable::create())
   ->declare(COH_PROPERTY(Person, FirstName, String::View)
      ->annotate(PortableProperty::create(Person::FIRST_NAME)))
   ->declare(COH_PROPERTY(Person, LastName, String::View)
      ->annotate(PortableProperty::create(Person::LAST_NAME)))
   ->declare(COH_PROPERTY(Person, Age, BoxHandle<const Integer32>)
      ->annotate(PortableProperty::create(Person::AGE)))
      );

10.8.2 Registering POF Annotated Objects

POF annotated objects must be registered as a user type using the COH_REGISTER_POF_ANNOTATED_CLASS macro. The following example registers a user type for an annotated Person object:

COH_REGISTER_POF_ANNOTATED_CLASS(1001, Person);

10.8.3 Enabling Automatic Indexing

POF annotations support automatic indexing which alleviates the need to explicitly assign and manage index values. The index value can be omitted whenever defining the PortableProperty annotation. Any property that does assign an explicit index value is not assigned an automatic index value. The automatic index algorithm can be described as follows:

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, use the COH_REGISTER_POF_ANNOTATED_CLASS_AI pre-processor macro when registering the user type. The following example registers a user type for an annotated Person object that uses automatic indexing:

COH_REGISTER_POF_ANNOTATED_CLASS_AI(1001, Person);

10.8.4 Providing a Custom Codec

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

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

class LinkedListCodec
   : public class_spec<LinkedListCodec,
      extends<Object>,
      implements<Codec> >
   {
   friend class factory<LinkedListCodec>;
 
   public:
      void encode(PofWriter::Handle hOut, int32_t nIndex, Object::View ovValue)
         const
         {
            hOut->writeCollection(nIndex, cast<Collection::View>(ovValue));
         }

      Object::Holder decode(PofReader::Handle hIn, int32_t nIndex) const
         {
            LinkedList::Handle hLinkeList = LinkedList::create();
            return hIn->readCollection(nIndex, hLinkeList);
         }
   };
COH_REGISTER_TYPED_CLASS(LinkedListCodec);

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

COH_REGISTER_CLASS(TypedClass<Person>::create()
   ->annotate(Portable::create())
   ->declare(COH_PROPERTY(Person, FirstName, String::View)
      ->annotate(PortableProperty::create(Person::FIRST_NAME)))
   ->declare(COH_PROPERTY(Person, LastName, String::View)
      ->annotate(PortableProperty::create(Person::LAST_NAME)))
   ->declare(COH_PROPERTY(Person, Age, BoxHandle<const Integer32>)
      ->annotate(PortableProperty::create(Person::ALIASES,
SystemClassLoader::getInstance()->loadByType(typeid(LinkedListCodec)) )))
);