Adding enum support to Flex AMF protocol

Introduction

Flex has a short learning curve for Java developers, who will find there lots of familiar language constructs and patterns. It also provides excellent remoting capabilities for Java programmers allowing transparent data transfer between ActionScript and Java 1.4 data types. With Java version 5 and above you have a lot of Java data structures that use enum and need marshaling to/from the Flex applications. In this article I will provide a working example of the ActionScript language extension for enum data type. We will discuss the issues common for adding language extensions to the bytecode machines/compilers. We will also extend LiveCycle DataServices AMF3 protocol to support native translation of classes between Java 5 and ActionScript 3.

Problem Statement

One of the most discussed limitations of LiveCycle Data Services (LCDS) serialization mechanism is a lack of serialization support for Java 5 enumerations. It is partially due to the fact that the code is supposed to work against Java 1.4, which did not have enums. This problem is acknowledged by Adobe, so one of your choices is just waiting for the next release of LCDS. But, alternatively, with a moderate effort you can extend existing data service classes and get the necessary functionality right away.

Let us review some facts about Flex AMF serialization to understand better why LCDS fails to serialize enumeration values out of the box. Flex serialization does not mimic Java’s serialization, it has only some similarities. Here are restrictions for remoting imposed by the default behavior of serialization:
1. Remote Java objects must have a public no-arguments constructor to be successfully de-serialized. With Java serialization, even objects without such constructor can be de-serialized as long as they implement java.io.Serializable marker interface.
2. Flex populates all public non-final non-transient instance fields upon deserialization and sets the JavaBean properties (exposed as pair of get/set methods) of the instance. Java serialization mechanism populates all non-transient instance fields (even the final ones). By the way, FDS and LCDS use JavaBean introspection mechanism to find out the object’s properties, so in theory you may use any method names besides classic getSomething / setSomething pair as long as you provide necessary BeanInfo class.
3. Flex may use externalization instead of serialization. Your Flex class has to implement flash.utils.IExternalizable interface and the corresponding remote Java class must implement java.io.Externalizable.

As you can see, the items 1 and 2 from list above rule out Java 5 enumerations from Flex serialization process. We will need a custom serialization on the Java side to serialize/deserialize enumerations. But first, take a look at the following simple Java enumeration:

public enum Gender { MALE, FEMALE }

Under the hood, Java compiler generates something like this (decompile the generated class with one of the free Java decompilers available on Web):

final public class Gender extends Enum<Gender> {
private Gender(String name, int ordinal) { super(name, ordinal); }
public static final Gender MALE = new Gender(“MALE”, 0);
public static final Gender FEMALE = new Gender(“FEMALE”, 1);
/* rest is omitted */
}

First of all, there is no public no-arguments constructor. Second, inherited “properties” name and ordinal of custom enumeration type are read-only. So even any Java 5 enumeration is a valid type for Java serialization due to explicit built-in support, it can’t be used as Flex remote class. Actually, we may not use externalization mechanism either while it’s impossible to restore internal read-only fields of enumeration value in implementation of java.io.Externalizable.readExternal().

Solution

What we would like propose is a small extension that allows using custom Java 5 enumerations as remote classes with minimal effort. In short, this is a drop-in extension for standard Flex AMFEndpoint classes that augments LCDS functionality with special support for Java 5 enumeration types.

Adobe engineers take extensibility aspect of LiveCycle Data Services 2.5 seriously. Besides numerous code-free configuration options available out-of-the-box, the API itself is very developer-friendly. The endpoints and AMF serialization framework have a lot of extensibility hooks and have nicely applied creational design patterns, so extending existing functionality is a joy.

The idea of our extension library is to intercept read/write operations with enumeration type as argument, substitute enumeration type with some wrapper that plays nicely with Flex serialization rules and pass this wrapper to super implementation. Please download and explore the source code at http://www.myflex.org/articles/downloads/farata-j5-messaging.src.zip.

You should start from com.farata.messaging.endpoints.J5AMFEndpoint class that redefines classes used for serialization/deserialization of AMF messages.

public class J5AMFEndpoint extends AMFEndpoint {

public J5AMFEndpoint() {
this(false);
}

public J5AMFEndpoint(final boolean enableManagement) {
super(enableManagement);
deserializerClass = J5AmfMessageDeserializer.class;
serializerClass = J5AmfMessageSerializer.class;
}
}

Then, overridden serialization classes will immediately lead you to extended AMF0/AMF3 input/output classes where actual enhancements are provided. For example:

public class J5Amf3Output extends Amf3Output {

public J5Amf3Output(final SerializationContext context) {
super(context);
}

@Override public void reset() {
super.reset();
enumTable.clear();
}

@Override public void writeObject(final Object o) throws IOException {
if (o instanceof Enum) {
@SuppressWarnings(“unchecked”)
final Enum<?> e = (Enum<?>)o;
EnumHolder holder = enumTable.get(o);
if (holder == null) {
holder = new EnumHolder(e);
enumTable.put(e, holder);
}
super.writeObject( holder );
}
else
super.writeObject(o);
}

final private IdentityHashMap<Enum<?>, EnumHolder> enumTable = new IdentityHashMap<Enum<?>, EnumHolder>();
}

But before trying to play with the library, it’s necessary to answer one question,”How enumeration in Flex should looks like?”

If you are a seasoned Java developer then you might recall that before Java 5 it was common to use “Type-safe enumeration” pattern to emulate current enumerations feature. The pattern works quite well, and, in fact what Java compiler currently generates for enumeration closely resembles this pattern. Below code shows Gender enumeration of pre-Java 5 era:

final public class Gender implements java.io.Serializable {
private static int INDEX = 0;

final private int ordinal;
final transient private String name;

private Gender(String name) {
this.name = name; this.ordinal = INDEX++;
}

public String name() { return name; }
public int ordinal() { return ordinal; }
public String toString() { return name; }

/*
hashCode and equals are not overwritten
while we need identity equality
provided by Object class by default
*/

public static Gender[] values() { return (Gender[])VALUES.clone(); }

public static Gender valueOf(String name) {
if (“MALE”.equals(name)) return MALE;
if (“FEMALE”.equals(name)) return FEMALE;
throw new IllegalArgumentException(“Unknown enumeration entry name: ” + name);
}

private void Object readResolve()
throws java.io.ObjectStreamException {
return VALUES[ordinal];
}

public static final Gender MALE = new Gender(“MALE”);
public static final Gender FEMALE = new Gender(“FEMALE”);

private static final Gender[] VALUES = {MALE, FEMALE};
}

Majority of the code above is simple to grasp. We restrict clients from creating arbitrary instances of a class with private constructor and expose a limited number of instances via class-level constants. The only tricky place here is readResolve method, which is absolutely necessary. One of the ideas of “Safe-type enumeration pattern” and current Java 5 enumerations is to enforce the identity equality comparison between enumeration values. So we must replace any new entry created by serialization mechanism with corresponding class-level constant to enable this feature. Note also, that during deserialization of this class in Java its constructor is not invoked and the ordinal field is assigned by JVM.

In effect there are only 2 instances of Gender per class-loader and any new temporal instance created during deserialization is immediately replaced by one of the constants above, so client code may safely rely on identity equality.

So, is “Type-safe enumeration” pattern reproducible in ActionScript3? Well, depending on your view of the “half-full/half-empty glass” problem, the answer varies between “yes, up to certain extent” and “not exactly”:

1. Private constructors in ActionScript3 are not available. So no compile-time checking can be applied; the best thing we can do to enforce the Singleton functionality is throwing a run-time Error from constructor if object is instantiated by client code rather then as part of class constant initialization. By the way, enumeration is a generic example of Singleton design pattern, and what is typically called singleton is a special case. The pattern itself is about limiting number of instances of specific class, be it either five or one instance.
2. Flex de-serialization mechanism always invokes a constructor of the target class. This is something we have to deal with.
3. Here’s the toughest issue: there is no mechanism like readResolve in Flex. As it is explained above, the readResolve method in Java allows to replace deserialized object with other instance. In case with type-safe enumerations or built-in Java 5 enums this replacement is an instance declared as static constant. So client code may safely compare deserialized enumeration values with the constants defined in the class by identity (reference equality). On other hand, in Flex after deserialization we end up with several instances of the same enumeration value. Even if all of them have exactly same properties’ values, the references are all different. Because of this you either should not rely on identity equality for “safe-type enumerations” or enforce some strict rules to convert deserialized values to constant values.

Ok, let us start this process over. First, here is Java enumeration we will map to Flex:

enum Priority { LOW, MEDIUM, HIGH }

Next, here are two several Action script classes plus namespace that will simplify our task:

package com.farata.as3.lang {
public namespace as3_lang = “http://www.faratasystems.com/as3/lang”;
}

package com.farata.as3.lang {
import flash.utils.Dictionary;

public class EnumClass {
private var _declaring:Boolean = false;
private var _nextIndex:int = 0;

public var valueMap:Dictionary = new Dictionary;
public var values:Array = [];

private var _elementClass:Class;
public function EnumClass(elementClass:Class):void {
_elementClass = elementClass;
}

internal function get declaring():Boolean { return _declaring; }
internal function get nextIndex():int { return _nextIndex++; }

public function declare(name:String):EnumBase {
_declaring = true;
const result:EnumBase = new _elementClass(name);
valueMap[name] = result;
values.push(result);
_declaring = false;
return result;
}
}
}

The EnumClass serves as meta-class for custom enumerations. It helps to declare specific enumeration constants as well as collect all declared constants in indexed and associative arrays, so we can easily get constant value by name/ordinal in custom subclasses. But most importantly, it enforces singleton rules: any enumeration entry may be created only via EnumClass declare method otherwise run-time error will be thrown. This rule has one exception, but we talk about this a bit later. Instead, let us take a look how this rule is applied in second helper class, EnumBase:

package com.farata.as3.lang {

import flash.utils.IExternalizable;
import flash.utils.IDataOutput;
import flash.utils.IDataInput;

public class EnumBase implements IExternalizable {
private var _ordinal:int;
[Transient]
private var _name:String;
[Transient]
private var _c:EnumClass;

public function EnumBase(C:EnumClass, name:String = null):void {
_c = C;

if (!name) {
_ordinal = -1;
return;
}

if ( !C.declaring )
throw Error(“Illegal attempt to create enum value”);

_ordinal = C.nextIndex;
_name = name;
}

public function get ordinal():int { return _ordinal; }
public function get name():String { return _name; }

final public function writeExternal(output:IDataOutput):void {
output.writeInt(_ordinal);
}

public function readExternal(input:IDataInput):void {
_ordinal = input.readInt();
_name = _c.values[_ordinal].name;
}

as3_lang function intern():EnumBase {
return _c.values[_ordinal];
}

public function equals(o:Object):Boolean {
if ( !(o is EnumBase) ) return false;
if ( this === o) return true;
const other:EnumBase = EnumBase(other);
return other._c === _c && other._ordinal === _ordinal;
}

public function valueOf():Number { return _ordinal; }
public function toString():String { return _name; }

as3_lang static function enumOf(entryClass:Class):EnumClass {
return new EnumClass(entryClass);
}
}
}

EnumBase is the base class for every custom enumeration. It provides necessary enumeration behavior like ordinal/name properties and serialization via flash.utils.IExternalizable mechanism. Please note that enumerations are serialized by ordinal for efficiency, so make sure that both Flex and Java enumeration constants are declared in the same order. Our library does not support any customizations of the serialization protocol on Java side (i.e. only ordinals are restored there), hence the method EnumBase.writeExternal is declared as final.

On other hand, you may need o restore some properties after deserialization on Flex side, so it’s allowed to override EnumBase.readExternal. In this case, the only possible source of information your code may access is a state of corresponding internal constant; please check how the name of enumeration entry is restored in EnumBase.readExternal: first we obtain a static constant with the same ordinal from the values array of EnumClass meta-class, then name property is copied from the constant instance.

To better understand why EnumBase/EnumClass are designed this way let us create a Priority enumeration in Flex:

package sample {
import com.farata.as3.lang.EnumBase;
import com.farata.as3.lang.EnumClass;

import com.farata.as3.lang.as3_lang;

[RemoteClass(alias="sample.Priority")]
public class Priority extends EnumBase {

public function Priority(name:String=null) { super(Self, name); }

public function intern():Priority {
return Priority(super.as3_lang::intern());
}

public static function valueOf(name:String):Priority {
return Self.valueMap[name];
}

private static function _(name:String):Priority {
return Priority( Self.declare(name) );
}

private static const Self:EnumClass = as3_lang::enumOf(Priority);

public static const LOW:Priority = _(“LOW”);
public static const MEDIUM:Priority = _(“MEDIUM”);
public static const HIGH:Priority = _(” HIGH “);

public static const values:Array = Self.values;
}
}

As you probably noticed, Priority class constructor declares the name argument as optional, and EnumBase has special guard condition to exit early when name is null. Again, this is done due to Flex serialization mechanism. During deserialization object constructor is always invoked and this constructor must either have no parameters or all parameters must have default values. By agreement, we don’t allow non-name constants to be declared, so when parameter is null we can assume that this is call done by serialization routine.

The other pair of methods probably contradicts each other, but in fact they both enable 2 options to handle non-unique-by-identity deserialized values. First one is Java-like equals, that lets 2 enumeration constants be compared by content. Second one is intern (named after Java’s String.intern) that returns canonical internal constant value. All internal values may be compared by identity, i.e. using regular equality operator. Notice, that this method is defined in custom namespace, so sub-classes may define own intern method in public namespace with correct return type. The trick is necessary while covariant return types are not allowed in Flex.

Finally, there is an instance valueOf method declared in EnumBase that returns ordinal. It has a quite interesting application. Also ActionScript does not support (yet) operator overloading, it handles specially relation operators (<, <=, >=, >) for custom objects. To perform the comparison, ActionScript gets the result of valueOf call and compares returned values.

The variables of built-in Date type are compared by internally stored time in milliseconds. In certain way, we copied the feature of Java enumerations – they are comparable by ordinal as well. As a neat result, we may execute tests like Priority.LOW <= Priority.HIGH and get the expected results. Flex compiler is smart enough to prevent us from comparing apples to oranges, only objects of same type may be compared, so neither Priority.LOW <= “HIGH” nor Priority.HIGH > Gender.MALE is going to work (assuming that Gender is a different enumeration)

Now we are ready to deploy and test enumeration example with LiveCycle Data Services. The working example can be found here: http://www.myflex.org/articles/fxenum/FlexEnum.html. Use right-click menu on a Flash control to browse and download the relevant Flex sources.

To create your local copy of this example or to enable support of enumerations in your own data services projects please download farata-j5-messaging.jar http://www.myflex.org/articles/downloads/farata-j5-messaging.jar and drop it into WEB-INF/lib folder of your web application with configured LiveCycle Data Services.

Note: Users of previous version of Flex/LiveCycle Data Services (FDS 2.0.1) need different version of this library available at http://www.myflex.org/articles/downloads/farata-j5-messaging-fds.jar.

Then open WEB-INF/flex/service-config.xml file and alter definitions of relevant channels:

<channel-definition id=”my-amf”
class=”mx.messaging.channels.AMFChannel”>
<endpoint
uri=”http://{server.name}:{server.port}/demo/messagebroker/amf”
class=”com.farata.messaging.endpoints.J5AMFEndpoint”/>
<properties>…</properties>
</channel-definition>

<channel-definition id=”my-secure-amf”
class=”mx.messaging.channels.SecureAMFChannel”>
<endpoint
uri=”https://{server.name}:9100/{context.root}/messagebroker/amfsecure”
class=” com.farata.messaging.endpoints.SecureJ5AMFEndpoint “/>
</channel-definition>

In other words, you need just to replace endpoints class(es) to have enumerations support in your remote services. As far as managed data services works over endpoints abstraction, you may use enumerations as properties of your managed objects with any data assembler, even with such complex one as HibernateAssembler.

Now download sample Java sources from http://www.myflex.org/articles/downloads/enum-sample-java.src.zip, build and deploy them to your server. You may download precompiled sample Java application from http://www.myflex.org/articles/downloads/enum-sample-java.src.zip. The sample contains a very simple POJO service that works directly with Priority enumeration and custom data transfer object Task. Obviously, the Task object has Priority as one of its fields.

Afterwards you need to tweak the file WEB-INF/lib/remoting-config.xml and add the destination myService like below:

<destination id=”myService”>
<properties>
<source>sample.MyService</source>
</properties>
</destination>

Finally, you are ready to download and build a sample Flex project (you can download the source code of the project at http://www.myflex.org/articles/downloads/enum-sample-java.src.zip).

Going forward

Draft of EcmaScript 4 also dictates the native support of enum in the future. ActionScipt 3 is compliant with EcmaScript 3 and most likely to add enum support in the next release. In meanwhile, Adobe has released their plans to open source Flex compiler by the end of the year(http://labs.adobe.com/wiki/index.php/Flex:Open_Source). That should allow Flex community to implement many of Java patterns natively in the language. For example, adding enumeration could have been done by adding and processing on compiler level additional annotations similar to [Managed], [Bindable], etc:

package sample {
[Enum values=”LOW,MEDIUM,HIGH” remoteClass="sample.Priority"]
public class Priority { }
}
You can also use available Java code generators like Clear Data Builder to automatically generate ActionScript enumeration classes out of the Java code
Conclusion
As you have seen in this article, adding a new base type and making it serializable is a reasonably simple operation. You can also easily extend the range of the datatypes that go across the wire today. Most importantly, you can use the approach described in this article and provide other custom extensions to AMF3 protocol for any type of the native data.

Valery Silaev

9 thoughts on “Adding enum support to Flex AMF protocol

  1. Thank you for publishing this approach to Enum serialization. We approached this problem a little bit differently. We created an abstract base class to be a wrapper for our Enums. A class extending flex.messaging.io.BeanProxy becomes responsible for serialization.

    Useful functions were added to the base Enum wrapper class which can respond to the Flex application upon initialization with all of the possible values for the corresponding enum, incliding a nicely formatted human-readable form of the name. This array is only maintained on the Java server side, and once received by the client, the values automatically populate our enum-aware UI controls with values. This is very useful for the enhanced ComboBox.

    Our AS class for each customized Enum is no more than an object which extends our Enum base class in Flex, with no additional fields or functions beyond a [Managed] and [Remote Class] definition.

  2. Your blog has been very helpful. We are using FDS not LCDS. I was hoping to look at the souce code for farata-j5-messaging-fds.jar, but I can’t seem to find it. Is it available?

  3. Great post. Exactly what we needed. Is the source code for farata-j5-messaging-fds.jar available anywhere?

  4. Thank you for this very nice solution. However, do you know if the serialization process has changed in BlazeDS? I tried to use your code in an application using BlazeDS and it does not work.

    Specifically, the J5AmfMessageSerializer attempts to override the setSerializationContext method in AmfMessageSerializer, but the method does not exist. I guess the serialization process has changed.

    Any ideas on how to change this code to work with BlazeDS?

  5. etho,

    Unfortunately, LCDS and BlazeDS use different server-side implementation. You can find more details in the following post: http://flexblog.faratasystems.com/?p=277. In short words, you can’t just drop the enum extension jar from this post and use it with BlazeDS. Probably we will add the BlazeDS version later, but no promises for now.

  6. I was able to figure it out. The problem was setSerializationContext was never getting called. Then, even when I made changes to have it be called, the outputStream was not being set on amfOut. This was leading a null pointer exception when serialization was taking place.

    Here is the quick fix I was able to come up with:

    public class J5AmfMessageSerializer extends AmfMessageSerializer {

    //…

    @Override public void initialize(SerializationContext context, OutputStream out, AmfTrace trace) {

    super.initialize(context, out, trace);
    amfOut = new J5Amf0Output(context);
    amfOut.setOutputStream(out);
    }

    //…
    //Note that setSerializationContext is still not called, but the same operation is performed in initialize(). I did this to ensure I had access to the proper OutputStream.

    }

    I don’t know enough about AMF to be sure this is the correct place to make the changes, but it has so far worked for me. Valery, you mention that Adobe makes the LCDS API “developer-friendly” but I was not able to find any information on the classes that you extend in your post. May I ask where you got this information?

    So far, I have only tried enum serialization, and am not sure if the same problem exists in the deserialization process.

    I hope this helps anyone having the same issues.

  7. I am using BlazeDS. I did same as etho said. I worked. But, having problem with StreamingAMFEndpoint. It registered both J5AmfMessageDeserializer and J5AmfMessageSerializer for StreamingAMFEndpoint. But enum isn’t working here.

  8. Hi,
    I’m trying to get this to work with BlazeDS 4. I’ve got it working from the server to the client, but when an Enum is sent from the client to the server I end up with the ‘Types cannot be instantiated without a public, no arguments constructor.’ error message.

    Any help would be very appreciated.

    Thanks, Chris

Comments are closed.