Fusion Middleware Documentation
Advanced Search


Developing Applications with Oracle Coherence
Close Window

Table of Contents

Show All | Collapse

20 Using Portable Object Format

This chapter provides instructions for using the Portable Object Format (POF) to serialize objects in Coherence. The instructions are specific to using POF for Java clients. For information on how to work with POF when building .NET extend clients, see "Building Integration Objects for .NET Clients" in Developing Remote Clients for Oracle Coherence. For information on how to work with POF when building C++ extend clients, see "Building Integration Objects for C++ Clients" in Developing Remote Clients for Oracle Coherence.

This chapter includes the following sections:

20.1 Understanding Serialization in Coherence

Coherence caches value objects. These objects may represent data from any source, either internal (such as session data, transient data, and so on) or external (such as a database, mainframe, and so on).

Objects placed in the cache must be serializable. Because serialization is often the most expensive part of clustered data management, Coherence provides different options for serializing/deserializing data:

  • com.tangosol.io.pof.PofSerializer – The Portable Object Format (also referred to as POF) is a language agnostic binary format. POF was designed to be incredibly efficient in both space and time and is the recommended serialization option in Coherence. See "Using the POF API to Serialize Objects".

  • java.io.Serializable – The simplest, but slowest option.

  • java.io.Externalizable – This requires developers to implement serialization manually, but can provide significant performance benefits. Compared to java.io.Serializable, this can cut serialized data size by a factor of two or more (especially helpful with Distributed caches, as they generally cache data in serialized form). Most importantly, CPU usage is dramatically reduced.

  • com.tangosol.io.ExternalizableLite – This is very similar to java.io.Externalizable, but offers better performance and less memory usage by using a more efficient I/O stream implementation.

  • com.tangosol.run.xml.XmlBean – A default implementation of ExternalizableLite (For more details, see the API Javadoc for XmlBean).

Note:

When serializing an object, Java serialization automatically crawls every visible object (by using object references, including collections like Map and List). As a result, cached objects should not refer to their parent objects directly (holding onto an identifying value like an integer is allowed). Objects that implement their own serialization routines are not affected.

20.2 Overview of POF Serialization

Serialization is the process of encoding an object into a binary format. It is a critical component to working with Coherence as data must be moved around the network. POF is a language agnostic binary format. POF was designed to be incredibly efficient in both space and time and has become a cornerstone element in working with Coherence. For more information on the POF binary stream, see Appendix E, "The PIF-POF Binary Format."

There are several options available for serialization including standard Java serialization, POF, and your own custom serialization routines. Each has their own trade-offs. Standard Java serialization is easy to implement, supports cyclic object graphs and preserves object identity. Unfortunately, it's also comparatively slow, has a verbose binary format, and restricted to only Java objects.

POF has the following advantages:

  • It's language independent with current support for Java, .NET, and C++.

  • It's very efficient, in a simple test class with a String, a long, and three ints, (de)serialization was seven times faster, and the binary produced was one sixth the size compared with standard Java serialization.

  • It's versionable, objects can evolve and have forward and backward compatibility.

  • It supports the ability to externalize your serialization logic.

  • It's indexed which allows for extracting values without deserializing the whole object. See "Using POF Extractors and POF Updaters".

20.3 Using the POF API to Serialize Objects

POF requires serialization routines that know how to serialize and deserialize an object. There are two interfaces available for serializing objects: the com.tangosol.io.pof.PortableObject interface and the com.tangosol.io.pof.PofSerializer interface. POF also supports annotations that automatically implement serialization with out having to implement the PortableObject or PofSerializer interfaces. See "Using POF Annotations to Serialize Objects" for details.

This section includes the following topics:

20.3.1 Implementing the PortableObject Interface

The PortableObject interface is an interface made up of two methods:

  • public void readExternal(PofReader reader)

  • public void writeExternal(PofWriter writer)

POF elements are indexed by providing a numeric value for each element that you write or read from the POF stream. It's important to keep in mind that the indexes must be unique to each element written and read from the POF stream, especially when you have derived types involved because the indexes must be unique between the super class and the derived class. The following example demonstrates implementing the PortableObject interface:

public void readExternal(PofReader in) 
   throws IOException 
   {
   m_symbol    = (Symbol) in.readObject(0);
   m_ldtPlaced = in.readLong(1);
   m_fClosed   = in.readBoolean(2);
   }
 
public void writeExternal(PofWriter out) 
   throws IOException 
   {
   out.writeObject(0, m_symbol);
   out.writeLong(1, m_ldtPlaced);
   out.writeBoolean(2, m_fClosed);
   }

20.3.2 Implementing the PofSerializer Interface

The PofSerializer interface provides a way to externalize the serialization logic from the classes you want to serialize. This is particularly useful when you do not want to change the structure of your classes to work with POF and Coherence. The PofSerializer interface is also made up of two methods:

  • public Object deserialize(PofReader in)

  • public void serialize(PofWriter out, Object o)

As with the PortableObject interface, all elements written to or read from the POF stream must be uniquely indexed. Below is an example implementation of the PofSerializer interface:

Example 20-1 Implementation of the PofSerializer Interface

public Object deserialize(PofReader in) 
   throws IOException 
   {
   Symbol symbol    = (Symbol)in.readObject(0);
   long   ldtPlaced = in.readLong(1);
   bool   fClosed   = in.readBoolean(2);
   
   // mark that reading the object is done
   in.readRemainder(null);
 
   return new Trade(symbol, ldtPlaced, fClosed);
   }
 
public void serialize(PofWriter out, Object o) 
   throws IOException 
   {
   Trade trade = (Trade) o;
   out.writeObject(0, trade.getSymbol());
   out.writeLong(1, trade.getTimePlaced());
   out.writeBoolean(2, trade.isClosed());
    
   // mark that writing the object is done
   out.writeRemainder(null);
   }

20.3.3 Guidelines for Assigning POF Indexes

Use the following guidelines when assigning POF indexes to an object's attributes:

  • Order your reads and writes: start with the lowest index value in the serialization routine and finish with the highest. When deserializing a value, perform reads in the same order as writes.

  • Non-contiguous indexes are acceptable but must be read/written sequentially.

  • When Subclassing reserve index ranges: index's are cumulative across derived types. As such, each derived type must be aware of the POF index range reserved by its super class.

  • Do not re-purpose indexes: to support Evolvable, it's imperative that indexes of attributes are not re-purposed across class revisions.

  • Label indexes: indexes that are labeled with a public static final int, are much easier to work with, especially when using POF Extractors and POF Updaters. See "Using POF Extractors and POF Updaters". Indexes that are labeled must still be read and written out in the same order as mentioned above.

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

This section includes the following topics:

20.3.4.1 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: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">
   ...
   <enable-references>true</enable-references>
</pof-config>

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

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

20.3.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 object that references the parent object 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 com.tangosol.io.pof.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:

public class Customer
   {
      private String m_sName;
      private Product m_product;
 
   public Customer(String sName)
      {
      m_sName = sName;
      }
 
   public Customer(String sName, Product product)
      {
      m_sName = sName;
      m_product = product;
      }

   public String getName()
      {
      return m_sName;
      }

   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 implements PofSerializer
   {
   @Override
   public void serialize(PofWriter pofWriter, Object o) throws IOException
   {
      Customer customer = (Customer) o;
      pofWriter.writeString(0, customer.getName());
      pofWriter.writeObject(1, customer.getProduct());
      pofWriter.writeRemainder(null);
   }
 
   @Override
   public Object deserialize(PofReader pofReader) throws IOException
      {
         String sName = pofReader.readString(0);
         Customer customer = new Customer(sName);

         pofReader.registerIdentity(customer);
         customer.setProduct((Product) pofReader.readObject(1));
         pofReader.readRemainder();
         return customer;
      }
   }

20.3.5 Registering POF Objects

Coherence provides the com.tangosol.io.pof.ConfigurablePofContext serializer class which is responsible for mapping a POF serialized object to an appropriate serialization routine (either a PofSerializer implementation or by calling through the PortableObject interface).

Once your classes have serialization routines, the classes are registered with the ConfigurablePofContext class using a pof-config.xml configuration file. The POF configuration file has a <user-type-list> element that contains a list of classes that implement PortableObject or have a PofSerializer associated with them. The <type-id> for each class must be unique, and must match across all cluster instances (including extend clients). See Appendix D, "POF User Type Configuration Elements," for detailed reference of the POF configuration elements.

The following is an example of a POF configuration file:

<?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>coherence-pof-config.xml</include>

      <!-- User types must be above 1000 -->
      <user-type>
         <type-id>1001</type-id>
         <class-name>com.examples.MyTrade</class-name>
         <serializer>
            <class-name>com.examples.MyTradeSerializer</class-name>
         </serializer>
      </user-type>
 
      <user-type>
        <type-id>1002</type-id>
        <class-name>com.examples.MyPortableTrade</class-name>
      </user-type>
   </user-type-list>
</pof-config>

Note:

Coherence reserves the first 1000 type-id's for internal use. As shown in the above example, the <user-type-list> includes the coherence-pof-config.xml file that is located in the root of the coherence.jar file. This is where Coherence specific user types are defined and should be included in all of your POF configuration files.

20.3.6 Configuring Coherence to Use the ConfigurablePofContext Class

Coherence can be configured to use the ConfigurablePofContext serializer class in three different ways based on the level of granularity that is required:

  • Per Service – Each service provides a full ConfigurablePofContext serializer class configuration or references a predefined configuration that is included in the operational configuration file.

  • All Services – All services use a global ConfigurablePofContext serializer class configuration. Services that provide their own configuration override the global configuration. The global configuration can also be a full configuration or reference a predefined configuration that is included in the operational configuration file.

  • JVM – The ConfigurablePofContext serializer class is enabled for the whole JVM.

20.3.6.1 Configure the ConfigurablePofContext Class Per Service

To configure a service to use the ConfigurablePofContext class, add a <serializer> element to a cache scheme in a cache configuration file. See "serializer" for a complete reference of the <serializer> element.

The following example demonstrates a distributed cache that is configured to use the ConfigurablePofContext class and defines a custom POF configuration file:

<distributed-scheme>
   <scheme-name>example-distributed</scheme-name>
   <service-name>DistributedCache</service-name>
   <serializer>
      <instance>
         <class-name>com.tangosol.io.pof.ConfigurablePofContext</class-name>
         <init-params>
            <init-param>
               <param-type>String</param-type>
               <param-value>my-pof-config.xml</param-value>
            </init-param>
         </init-params>
      </instance>
   </serializer>
</distributed-scheme>

The following example references the default definition in the operational configuration file. Refer to "serializer" to see the default ConfigurablePofContext serializer definition.

<distributed-scheme>
    <scheme-name>example-distributed</scheme-name>
    <service-name>DistributedCache</service-name>
    <serializer>pof</serializer>
 </distributed-scheme>

20.3.6.2 Configure the ConfigurablePofContext Class for All Services

To globally configure the ConfigurablePofContext class for all services, add a <serializer> element within the <defaults> element in a cache configuration file. Both of the below examples globally configure a serializer for all cache scheme definitions and do not require any additional configuration within individual cache scheme definitions. See "defaults" for a complete reference of the <defaults> element.

The following example demonstrates a global configuration for the ConfigurablePofContext class and defines a custom POF configuration file:

<?xml version='1.0'?>

<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
   xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config
   coherence-cache-config.xsd">
   <defaults>
      <serializer>
         <instance>
            <class-name>com.tangosol.io.pof.ConfigurablePofContext</class-name>
            <init-params>
               <init-param>
                  <param-type>String</param-type>
                  <param-value>my-pof-config.xml</param-value>
               </init-param>
            </init-params>
         </instance>
      </serializer>
   </defaults>
   ...

The following example references the default definition in the operational configuration file. Refer to "serializer" to see the default ConfigurablePofContext serializer definition.

<?xml version='1.0'?>

<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
   xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config
   coherence-cache-config.xsd">
   <defaults>
      <serializer>pof</serializer>
   </defaults>
   ...

20.3.6.3 Configure the ConfigurablePofContext Class For the JVM

An entire JVM instance can be configured to use POF using the following system properties:

  • tangosol.pof.enabled=true - Enables POF for the entire JVM instance.

  • tangosol.pof.config=CONFIG_FILE_PATH - The path to the POF configuration file you want to use. If the files is not in the classpath, then it must be presented as a file resource (for example, file:///opt/home/coherence/mycustom-pof-config.xml).

20.4 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 PortableObject and PofSerializer interfaces and reduce the amount of time and code that is required to make objects serializable.

This section includes the following topics:

20.4.1 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 member variable or method accessor as a POF serialized attribute. 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, method, and properties and assigning explicit property index values. See "Guidelines for Assigning POF Indexes" for additional details on POF indexing.

@Portable
public class Person
   {
   @PortableProperty(0)
   public String getFirstName()
      {
      return m_firstName;
      }
 
   private String m_firstName;

   @PortableProperty(1)
   private String m_lastName;

   @PortableProperty(2)
   private int m_age;
}

20.4.2 Registering POF Annotated Objects

POF annotated objects, like all POF objects, must be registered in a pof-config.xml file within a <user-type> element. See Appendix D, "POF User Type Configuration Elements," for a detailed reference of the POF configuration elements. As an alternative to manually creating a POF configuration file, the POF Configuration Generator tool can be used to automatically create a POF configuration file based on objects that use the @Portable annotation. For details on the POF Configuration Generator tool, see "Generating a POF Configuration File".

POF annotated objects use the PofAnnotationSerializer serializer if an object does not implement PortableObject 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: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>coherence-pof-config.xml</include>

      <!-- User types must be above 1000 -->
      <user-type>
         <type-id>1001</type-id>
         <class-name>com.examples.Person</class-name>
      </user-type>
   </user-type-list>
</pof-config>

20.4.3 Generating a POF Configuration File

The POF Configuration Generator command line tool automatically creates a POF configuration file that includes user type entries for the classes that contain the @Portable annotation. The tool is an alternative to manually creating a POF configuration file and is ideal as part of a build process.

Start the POF Configuration Generator command line tool by using the COHERENCE_HOME/bin/pof-config-gen script (.cmd or .sh) or by directly running the com.tangosol.io.pof.generator.Executor class. Use the -help argument for detailed usage instructions. The usage is as follows:

pof-config-gen [OPTIONS] -root

The -root argument is required and lists the locations (separated directories, JAR files, or GAR file) to scan for POF annotated classes. A user type entry and ID is generated for each annotated class that is found and the resulting pof-config.xml file is written to the current working directory. For example:

pof-config-gen.cmd -root c:\src\myPofClasses.jar

Note:

If you specify a GAR file as the root, the output is a new GAR file with a count suffix (filename-n.gar) that includes the generated POF configuration file.

The following optional arguments may be provided:

  • -out: Use the -out argument to specify either the path to an output directory or a path and filename. The default output directory is the working directory. The default filename if only a directory is specified is pof-config.xml. If a directory is specified and a pof-config.xml file already exists, then a new file is created with a count suffix (pof-config-n.xml). If a path and filename are specified and the file currently exists, then the file is overwritten.

  • -config: Use the -config argument to specify the path and filename of an existing POF configuration file from which existing user types must be added to the generated POF configuration file. Existing user type IDs are retained in the generated file. This argument can be used to support backwards compatibility when generating a POF configuration file multiple times.

  • -include: Use the -include argument to specify whether an existing POF configuration file (as specified by the -config argument) should only be referenced in the generated POF configuration file. The argument results in an <include> element that references the existing file. Existing user types and IDs are not recreated in the generated file. At runtime, the referenced file must be located in the classpath.

    Note:

    A reference to the Coherence-specific POF configuration file (coherence-pof-config.xml) is automatically added to the generated POF configuration file if it is not found in an existing POF configuration file and does not need to be added using the -include argument.

  • -packages: Use the -packages argument to constrain the class scan to specific packages. The packages are entered as a comma separated list.

  • -startTypeId: Use the -startTypeId argument to specify the user type ID number from which to start allocating IDs. IDs up to 1000 are reserved for Coherence-specific types and cannot be used.

The following example scans the c:\classes\pof directory and creates a new POF configuration file called my-pof-config.xml in the c:\tmp directory that includes (by reference) the existing c:\tmp\pof-config.xml file.

pof-config-gen.cmd -out c:\tmp\my-pof-config.xml -config c:\tmp\pof-config.xml
 -include -root c:\classes\pof

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

Property 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 fAutoIndex 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>com.examples.Person</class-name>
   <serializer>
      <class-name>com.tangosol.io.pof.PofAnnotationSerializer</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>boolean</param-type>
            <param-value>true</param-value>
         </init-param>
      </init-params>
   </serializer>
</user-type>

20.4.5 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 com.tangosol.io.pof.reflect.Codec interface. The following example demonstrates a codec that defines the concrete implementation of a linked list type:

public static class LinkedListCodec implements Codec
   {
   public Object decode(PofReader in, int index) throws IOException
      {
      return (List<String>) in.readCollection(index, new LinkedList<String>());
      }
   public void encode(PofWriter out, int index, Object value) throws IOException
      {
      out.writeCollection(index, (Collection) value);
      {
   }

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:

@PortableProperty(codec = LinkedListCodec.class)
private List<String> m_aliases;

20.5 Using POF Extractors and POF Updaters

In Coherence, the ValueExtractor and ValueUpdater interfaces are used to extract and update values of objects that are stored in the cache. The PofExtractor and PofUpdater interfaces take advantage of the POF indexed state to extract or update an object without the requirement to go through the full serialization/deserialization routines.

PofExtractor and PofUpdater adds flexibility in working with non-primitive types in Coherence. For many extend client cases, a corresponding Java classes in the grid is no longer required. Because POF extractors and POF updaters can navigate the binary, the entire key/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. However, a corresponding 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.

20.5.1 Navigating a POF object

Due to the fact that POF is indexed, it's possible to quickly traverse the binary to a specific element for extraction or updating. It's the responsibility of the PofNavigator interface to traverse a POF value object and return the desired POF value object. Out of the box, Coherence provides a SimplePofPath class that can navigate a POF value based on integer indexes. In the simplest form, provide the index of the attribute to be extracted/updated.Consider the following example:

public class Contact
        implements PortableObject
    {
    ...
    // ----- PortableObject interface ---------------------------------------
 
    public void readExternal(PofReader reader)
            throws IOException
        {
        m_sFirstName     = reader.readString(FIRSTNAME);
        m_sLastName      = reader.readString(LASTNAME);
        m_addrHome       = (Address) reader.readObject(HOME_ADDRESS);
        m_addrWork       = (Address) reader.readObject(WORK_ADDRESS);
        m_mapPhoneNumber = reader.readMap(PHONE_NUMBERS, null);
        }
 
    public void writeExternal(PofWriter writer)
            throws IOException
        {
        writer.writeString(FIRSTNAME, m_sFirstName);
        writer.writeString(LASTNAME, m_sLastName);
        writer.writeObject(HOME_ADDRESS, m_addrHome);
        writer.writeObject(WORK_ADDRESS, m_addrWork);
        writer.writeMap(PHONE_NUMBERS, m_mapPhoneNumber);
        }
 
    ....
 
    // ----- constants -------------------------------------------------------
 
    public static final int FIRSTNAME = 0;
    public static final int LASTNAME = 1;
    public static final int HOME_ADDRESS = 2;
    public static final int WORK_ADDRESS = 3;
    public static final int PHONE_NUMBERS = 4;
 
    ...
}

Notice that there's a constant for each data member that is being written to and from the POF stream. This is an excellent practice to follow as it simplifies both writing your serialization routines and makes it easier to work with POF extractors and POF updaters. By labeling each index, it becomes much easier to think about the index. As mentioned above, in the simplest case, the work address can be pulled out of the contact by using the WORK_ADDRESS index. The SimplePofPath also allows using an Array of ints to traverse the PofValues. For example, to get the zip code of the work address use [WORK_ADDRESS, ZIP]. The example are discussed in more detail below.

20.5.2 Using POF Extractors

POF extractors are typically used when querying a cache and improves query performance. For example, using the class demonstrated above, to query the cache for all contacts with the last names Jones, the query is as follows:

ValueExtractor veName = new PofExtractor(String.class, Contact.LASTNAME);
Filter         filter = new EqualsFilter(veName, "Jones");
 
// find all entries that have a last name of Jones
Set setEntries = cache.entrySet(filter);

In the above case, PofExtractor has a convenience constructor that uses a SimplePofPath to retrieve a singular index, in our case the Contact.LASTNAME index. To find all contacts with the area code 01803, the query is as follows:

ValueExtractor veZip = new PofExtractor(
   String.class, new SimplePofPath(new int[] {Contact.WORK_ADDRESS, Address.ZIP}));
 
Filter filter = new EqualsFilter(veZip, "01803");
 
// find all entries that have a work address in the 01803 zip code
Set setEntries  = cache.entrySet(filter);

Notice that in the previous examples, the PofExtractor constructor has a first argument with the class of the extracted value or null. The reason for passing type information is that POF uses a compact form in the serialized value when possible. For example, some numeric values are represented as special POF intrinsic types in which the type implies the value. As a result, POF requires the receiver of a value to have implicit knowledge of the type. PofExtractor uses the class supplied in the constructor as the source of the type information. If the class is null, PofExtractor infers the type from the serialized state, but the extracted type may differ from the expected type. String types, in fact, can be correctly inferred from the POF stream, so null is sufficient in the previous examples. In general, however, null should not be used.

20.5.3 Using POF Updaters

POF updaters work in the same way as POF extractors except that they update the value of an object rather than extract it. To change all entries with the last name of Jones to Smith, use the UpdaterProcessor class as follows:

ValueExtractor veName  = new PofExtractor(String.class, Contact.LASTNAME);
Filter         filter  = new EqualsFilter(veName, "Jones");
ValueUpdater   updater = new PofUpdater(Contact.LASTNAME);

// find all Contacts with the last name Jones and change them to have the last
// name "Smith"

cache.invokeAll(filter, new UpdaterProcessor(updater, "Smith"));

Note:

while these examples operate on String based values, this functionality works on any POF encoded value.

20.6 Serializing Keys Using POF

Key objects, like value objects, can be serialized using POF. However, the following issues must be considered:

  • POF defines a cross-platform object format, it cannot always provide a symmetrical conversion. That is, when a serialized object is deserialized, the object type is different than the original type. This occurs because some data types in Java do not have equivalents in the .NET and C++ platforms. As a result, avoid using classes that potentially have an asymmetrical POF conversion as keys, or parts of keys, for caches and other Java collections.

  • Avoided using the java.util.Date type. POF is designed to serialize to java.sql.Timestamp (which extends java.util.Date). The wire formats for those two classes are identical, and a deserialization of that wire representation always results in a java.sql.Timestamp instance. Unfortunately, the equals method of those two base classes breaks the symmetry requirement for keys in Java collections. That is, if you have two objects: D (of java.util.Date) and T (of java.sql.Timestamp) that are equivalent from the POF wire format perspective, then D.equals(T) yields true, while T.equals(D) yields false. Therefore, the use of java.util.Date must be avoided. Use a Long representation of the date or the java.sql.Timestamp type to avoid breaking the key symmetry requirement.

  • Keys that are using POF object references cannot be serialized. In addition, POF object references support circular references. Therefore, you must ensure that your key class does not have circular references.