The Dark World of IExternizable

I never worked with Remote Objects much; I stuck with SOAP, REST., and AMF. What I did learn was how custom objects can represent themselves to an AMF stream, and to local-storage SharedObjects and fileStreams too.

ActionScript has built-in serialization (compatible with java.io.Externalizable interface). While this is very obvious streaming data over AMF pipelines (to LCDS, AMFPHP, Zend, etc.), it quietly makes saving and restoring data pretty painless. These operations use the streaming protocol:

  • RemoteObjects
  • Streaming objects to ByteArray.writeObject()
  • SharedObject (local storage)
  • EncryptedSharedObject

Simple Streaming

Most values and object will stream to and from AMF just fine. To effect this simple protocol, put the [RemoteClass("com.company.application.section.ClassName")] metadata tag (Flex projects only) above the class declaration so the parser knows what class to create. The class constructor must have defaults for all its arguments (or no arguments), and all public properties must writable (i.e. they must be vars or have mutators — aka “setters”).

The streamed properties that are the ones that are public var or have both a public accessor and a mutator. If a class has public properties one does not wish to stream, mark those properies with the [Transient] metadata tag.

[RemoteClass("com.company.application.section.Foo")]
public class Foo 

    public var name : String = "";
    public var id : String = "";

    [Transient]
    public var contact : Contact = null;
}

The streaming system streams and object in by creating a new instance and then setting each property in turn (it will not use the constructor’s parameters).

Streaming Read-Only and Private Properties

If some properties don’t have public mutators (i.e. they are publicly read-only), or one wants to stream non-public attributes, one can write custom serializing and de-serializing routines by implementing the interface IExternizable. It requires two methods:

[RemoteClass("com.company.application.section.Foo")]
public class Foo implements IExternizable
{
    public var name : String  = "";

    public function get id() : String
    {
        return _id;
    }
    private var _id : String = "";

    public function readExternal(input : IDataInput) : void
    {
        name = input.readObject() as String;
        _id = input.readUnsignedInt();

                // this object might need to be IExternizable too
        _contact = input.readObject() as Contact;
    }
    public function writeExternal(output : IDataOutput) : void
    {
        output.writeObject(name);   //  same sequence as readExternal()
        output.writeUnsignedInt(_id);
        output.writeObject(_contact);
    }
}

The IDataInput has no information about its data, so one cannot, for example, test to see if an integer value is less than 127 and safely call writeByte() instead of writeInt() (to save space) because readExternal() has no way of knowing if it should call readByte() or readInt(). One can stream format and version information out first, and use that information to determine the format of the rest of the data when streaming it in (although this smacks of variable record types from Days Long Past). It’s much easier to always stream the same object types in the same sequence, even writing out some empty strings and nulls as placeholders when necessary.

Any class implementing IExternizable must explicitly stream all its data because the built-in streaming is disabled . Note that the example above will stream the entire nested _contact object out as part of the Foo object stream. If  Contact implemented IExternizable, it’s readExternal() and writeExternal() would handle its streaming too.

Streaming References

If a class contains a references to another object, one can stream out an id and resolve the id after it streams in. One can resolve that id into an object reference either explicitly when both the referer and the cross-reference (e.g. the list of all Contacts) are ready, or do it as a lazy-loading accessor:

[RemoteClass("com.company.application.section.Foo")]
public class Foo implements IExternizable
{
    public var name : String  = "";

    public function get id() : String
    {
        return _id;
    }
    private var _id : String = "";

    public function get contact() : Contact
    {
        if (_contact == null)
        {
            if (_contactId != "")
            {
//
// some mechanism to find the contact by Id; it can return null
//
                _contact = Contact.lookupId(_contactId);
                _contactId = "";
            }
        }

        return _contact;
    }
    private var _contact : Contact = null;
    private var _contactId : String = "";

    public function readExternal(input : IDataInput) : void
    {
        name = input.readObject() as String;
        _id = input.readInt();
        _contactId = input.readObject() as String;
    }

    public function writeExternal(output : IDataOutput) : void
    {
         output.writeObject(name); // same sequence as readExternal()
         output.writeInt(_id);

         if (_contact == null)
            output.writeObject(""); // placeholder
        else
            output.writeObject(_contact.Id);   // just the Id
    }
}

Adding IExternizable to Existing Objects

If you add IExternizable to an object that has been saved and will be retrieved, you need to read the stream in the same sequence (and don’t fail if some new property is not in the stream). You can determine the sequence of properties before adding the writeExternal() by creating mutators for all the public properties, and then debugging while an instance comes in.

See Adobe Documentation on IExternizable

Leave a Reply

Your email address will not be published. Required fields are marked *