Object mismatch interface

The ObjectMismatchTrigger interface is show in Example 8.1, “ObjectMismatchTrigger interface”.

Example 8.1. ObjectMismatchTrigger interface

public interface ObjectMismatchTrigger
{
    public void writeObjectToStream(
        ManagedObjectStreamClass remoteClassDescriptor,
        ObjectOutputStream out) throws IOException;

    public void readObjectFromStream(
        ManagedObjectStreamClass remoteClassDescriptor,
        ObjectInputStream in) throws IOException;
}

It defines these methods:

These methods are only called on the node where the new class version is installed. In all cases, the remoteClassDescriptor parameter contains a description of the old version of the class. For example, in Figure 8.1, “Object mismatch method invocation”, the remoteClassDescriptor parameter always contains a description of O1.

Object mismatch method invocation

Figure 8.1. Object mismatch method invocation


Versions

The ManagedObjectStreamClass provides access to the remote serialVersionUID for a class using the getSerialVersionUID() method. The version information can be used to perform conditional mapping based on the actual class version on a remote node. This makes it possible to support multiple versions across a cluster. The ObjectMismatchTrigger interface can use the version information associated with the remote class to conditionally map the differences.

[Warning]

If no serialVersionUID is set in a class, getSerialVersionUID() returns a value of 0L. To ensure that class version numbers are unambiguous, a serialVersionUID value of 0L should never be used.

When supporting multiple deployed versions of a class, the upgrade utility must be run against the new class version and the most current old version. Not doing this may cause unresolved version mismatches are runtime.

Field mapping

The ManagedObjectStreamClass contains an ordered array of all fields, starting with the top most parent, to the current child class where an ObjectMismatchTrigger method is called. These fields must be processed in order. This is done using the ManagedObjectStreamClass.getFields() method.

The general approach to field mapping using the ManagedObjectStreamClass class is to iterate over all of the fields returned by the ManagedObjectStreamClass and map them into the new field definitions. The ManagedObjectStreamClass always contains the field definitions for the old class version.

An alternative approach to field mapping is to use the ObjectInputStream.GetField and ObjectOutputStream.PutField classes. These classes provide random access to fields by name. Again, the ObjectInputStream.GetField and ObjectOutputStream.PutField always contain the field definitions for the old class version.

[Note]

The use of the ManagedObjectStreamClass to directly serialize and de-serialize a stream will be more efficient than using ObjectInputStream.GetField and ObjectOutputStream.PutField since fewer allocations and data copies are required.

See the section called “Inheritance” for details on how inheritance affects field processing. A field mapping example is shown in Example 8.3, “Updated class definition”. This example shows the use of both the ManagedObjectStreamClass and the ObjectInputStream.GetField class.

Inheritance

Classes that extend other classes, must ensure that the parent class processes the object stream before the child. This can be done a couple of ways:

  • Use super to call the parent's implementation of the ObjectMismatchTrigger method. This will only work if the inheritance hierarchy hasn't changed between the old and new class versions.

  • Directly set the parent's field values. This is the only option if the inheritance hierarchy has changed between the old and new class versions. This does imply that a child must have access to all parent class fields either because they are protected, or there are setters available.

When an ObjectMismatchTrigger method is executed, the method always has complete access to any parent class fields in the old class version.

ObjectMismatchTrigger methods are only called on the leaf type of an inheritance hierarchy. They are not called on any of the parent types.

Here is an example using a call to super to populate a parent's fields:

//
//  Both old and new class versions extend Base
//
class Child extends Base
{
    public void readObjectFromStream(
        ManagedObjectStreamClass remoteClassDesc,
        ObjectInputStream in) throws IOException
    {
        //
        // Call the parent to process its fields (like a constructor,
        // this must be called before reading the child fields).
        //
        super.readObjectFromStream(remoteClassDesc, in);

        //
        // Process child fields
        //
    }
}

Here is another example, where the class hierarchy has changed between the old and new class version.

// VERSION 1:
@Managed
class InheritBase implements ObjectMismatchTrigger, Serializable
{
    int    int_val;
}
class InheritChild extends InheritBase
{
    byte []    byte_array;
}
class Inherit extends InheritChild
{
    String []   str_array;
}

//
//  VERSION 2: collapsed the class hierachy into a single class
//
@Managed
class Inherit implements ObjectMismatchTrigger, Serializable
{
    int        int_val;
    byte []    byte_array;
    String []  str_array;

    public void readObjectFromStream(
        ManagedObjectStreamClass remoteClassDesc,
        ObjectInputStream in) throws IOException
    {
        //
        // Process all fields directly here, this includes
        // the parent class fields (InheritBase & InheritChild)
        // in VERSION 1
        //
    }
}

Final fields

Reflection must be used to set final fields in a class in the readObjectFromStream method. The Java language prohibits the setting of final fields after an object is created - but that is exactly what is required when mapping a final field in the readObjectFromStream method.

To set a final field in readObjectFromStream requires code like the following, where field id is defined as final.

try
{
    java.lang.reflect.Field f = this.getClass().getDeclaredField("id");
    f.setAccessible(true);
    f.set(this, val);
}
catch (NoSuchFieldException ex)
{
    throw new IOException(ex);
}
catch (IllegalAccessException ex)
{
    throw new IOException(ex);
}

Mapping errors

Errors processing an object stream are reported using a com.kabira.platform.DataError exception. This exception contains information on the value that was in error. For example:

com.kabira.platform.DataError: Stream Corrupted: 
   invalid type code: 0x77, expected type code: 0x70

The type code values are defined by the Java Serialization specification Object Serialization Stream Protocol chapter. The constant definitions are also found in the java.io.ObjectStreamConstants javadoc.

The error above indicates that a TC_BLOCKDATA type code was seen when a TC_NULL was expected. This error was caused by attempting to write an integral type (out.writeBytes()) when a string was expected (out.writeObject()).