Serialization between ActionScript and Java

LiveCycle Data Services and Flex let you serialize data between ActionScript (AMF 3) and Java in both directions.

Data conversion from ActionScript to Java

When method parameters send data from a Flex application to a Java object, the data is automatically converted from an ActionScript data type to a Java data type. When LiveCycle Data Services searches for a suitable method on the Java object, it uses further, more lenient conversions to find a match.

Simple data types on the client, such as Boolean and String values, typically exactly match a remote API. However, Flex attempts some simple conversions when searching for a suitable method on a Java object.

An ActionScript Array can index entries in two ways. A strict Array is one in which all indices are Numbers. An associative Array is one in which at least one index is based on a String. It is important to know which type of Array you are sending to the server, because it changes the data type of parameters that are used to invoke a method on a Java object. A dense Array is one in which all numeric indices are consecutive, with no gap, starting from 0 (zero). A sparse Array is one in which there are gaps between the numeric indices; the Array is treated like an object and the numeric indices become properties that are deserialized into a java.util.Map object to avoid sending many null entries.

The following table lists the supported ActionScript (AMF 3) to Java conversions for simple data types:

ActionScript type (AMF 3)

Deserialization to Java

Supported Java type binding

Array (dense)

java.util.List

java.util.Collection, Object[ ] (native array)

If the type is an interface, it is mapped to the following interface implementations

  • List becomes ArrayList

  • SortedSet becomes TreeSet

  • Set becomes HashSet

  • Collection becomes ArrayList

A new instance of a custom Collection implementation is bound to that type.

Array (sparse)

java.util.Map

java.util.Map

Boolean

String of "true" or "false"

java.lang.Boolean

Boolean, boolean, String

flash.utils.ByteArray

byte []

 

flash.utils.IExternalizable

java.io.Externalizable

 

Date

java.util.Date

(formatted for Coordinated Universal Time (UTC))

java.util.Date, java.util.Calendar, java.sql.Timestamp, java.sql.Time, java.sql.Date

int/uint

java.lang.Integer

java.lang.Double, java.lang.Long, java.lang.Float, java.lang.Integer, java.lang.Short, java.lang.Byte, java.math.BigDecimal, java.math.BigInteger, String, primitive types of double, long, float, int, short, byte

null

null

primitives

Number

java.lang.Double

java.lang.Double, java.lang.Long, java.lang.Float, java.lang.Integer, java.lang.Short, java.lang.Byte, java.math.BigDecimal, java.math.BigInteger, String, 0 (zero) if null is sent, primitive types of double, long, float, int, short, byte

Object (generic)

java.util.Map

If a Map interface is specified, creates a new java.util.HashMap for java.util.Map and a new java.util.TreeMap for java.util.SortedMap.

String

java.lang.String

java.lang.String, java.lang.Boolean, java.lang.Number, java.math.BigInteger, java.math.BigDecimal, char[], enum, any primitive number type

typed Object

typed Object

when you use [RemoteClass] metadata tag that specifies remote class name. Bean type must have a public no args constructor.

typed Object

undefined

null

null for Object, default values for primitives

XML

org.w3c.dom.Document

org.w3c.dom.Document

XMLDocument

(legacy XML type)

org.w3c.dom.Document

org.w3c.dom.Document

You can enable legacy XML support for the XMLDocument type on any channel defined in the services-config.xml file. This setting is only important for sending data from the server back to the client; it controls how org.w3c.dom.Document instances are sent to ActionScript. For more information, see Configuring AMF serialization on a channel.

Primitive values cannot be set to null in Java. When passing Boolean and Number values from the client to a Java object, Flex interprets null values as the default values for primitive types; for example, 0 for double, float, long, int, short, byte, \u0000 for char, and false for boolean. Only primitive Java types get default values.

LiveCycle Data Services handles java.lang.Throwable objects like any other typed object. They are processed with rules that look for public fields and bean properties, and typed objects are returned to the client. The rules are like normal bean rules except they look for getters for read-only properties. This lets you get more information from a Java exception. If you require legacy behavior for Throwable objects, you can set the legacy-throwable property to true on a channel; for more information, see Configuring AMF serialization on a channel.

You can pass strict Arrays as parameters to methods that expect an implementation of the java.util.Collection or native Java Array APIs.

A Java Collection can contain any number of Object types, whereas a Java Array requires that entries are the same type (for example, java.lang.Object[ ], and int[ ]).

LiveCycle Data Services also converts ActionScript strict Arrays to appropriate implementations for common Collection API interfaces. For example, if an ActionScript strict Array is sent to the Java object method public void addProducts(java.util.Set products), LiveCycle Data Services converts it to a java.util.HashSet instance before passing it as a parameter, because HashSet is a suitable implementation of the java.util.Set interface. Similarly, LiveCycle Data Services passes an instance of java.util.TreeSet to parameters typed with the java.util.SortedSet interface.

LiveCycle Data Services passes an instance of java.util.ArrayList to parameters typed with the java.util.List interface and any other interface that extends java.util.Collection. Then these types are sent back to the client as mx.collections.ArrayCollection instances. If you require normal ActionScript Arrays sent back to the client, you must set the legacy-collection element to true in the serialization section of a channel-definition's properties; for more information, see Configuring AMF serialization on a channel.

Explicitly mapping ActionScript and Java objects

For Java objects that LiveCycle Data Services does not handle implicitly, LiveCycle Data Services uses value objects, also known as transfer objects, to send data between client and server. For Java objects on the server side, values found in public bean properties with get/set methods and public variables are sent to the client as properties on an Object. Private properties, constants, static properties, and read-only properties are not serialized. For ActionScript objects on the client side, public properties defined with the get/set accessors and public variables are sent to the server.

LiveCycle Data Services uses the standard Java class, java.beans.Introspector, to get property descriptors for a Java bean class. It also uses reflection to gather public fields on a class. It uses bean properties in preference to fields. The Java and ActionScript property names should match. Native Flash Player code determines how ActionScript classes are introspected on the client.

In the ActionScript class, you use the [RemoteClass(alias=" ")] metadata tag to create an ActionScript object that maps directly to the Java object. The ActionScript class to which data is converted must be used or referenced in the MXML file for it to be linked into the SWF file and available at run time. A good way to do this is by casting the result object, as the following example shows:

var result:MyClass = MyClass(event.result);

The class itself should use strongly typed references so that its dependencies are also linked.

The following example shows the source code for an ActionScript class that uses the [RemoteClass(alias=" ")] metadata tag:

package samples.contact { 
    [Bindable] 
    [RemoteClass(alias="samples.contact.Contact")] 
    public class Contact { 
        public var contactId:int; 
 
        public var firstName:String; 
 
        public var lastName:String; 
 
        public var address:String; 
 
        public var city:String; 
 
        public var state:String; 
 
        public var zip:String; 
    } 
}

You can use the [RemoteClass] metadata tag without an alias if you do not map to a Java object on the server, but you do send back your object type from the server. Your ActionScript object is serialized to a special Map object when it is sent to the server, but the object returned from the server to the clients is your original ActionScript type.

To restrict a specific property from being sent to the server from an ActionScript class, use the [Transient] metadata tag above the declaration of that property in the ActionScript class.

Data conversion from Java to ActionScript

An object returned from a Java method is converted from Java to ActionScript. LiveCycle Data Services also handles objects found within objects. LiveCycle Data Services implicitly handles the Java data types in the following table.

Java type

ActionScript type (AMF 3)

enum (JDK 1.5)

String

java.lang.String

String

java.lang.Boolean, boolean

Boolean

java.lang.Integer, int

int

If value < 0xF0000000 || value > 0x0FFFFFFF, the value is promoted to Number due to AMF encoding requirements.

java.lang.Short, short

int

If i < 0xF0000000 || i > 0x0FFFFFFF, the value is promoted to Number.

java.lang.Byte, byte[]

int

If i < 0xF0000000 || i > 0x0FFFFFFF, the value is promoted to Number.

java.lang.Byte[]

flash.utils.ByteArray

java.lang.Double, double

Number

java.lang.Long, long

Number

java.lang.Float, float

Number

java.lang.Character, char

String

java.lang.Character[], char[]

String

java. math.BigInteger

String

java.math.BigDecimal

String

java.util.Calendar

Date

Dates are sent in the Coordinated Universal Time (UTC) time zone. Clients and servers must adjust time accordingly for time zones.

java.util.Date

Date

Dates are sent in the UTC time zone. Clients and servers must adjust time accordingly for time zones.

java.util.Collection (for example, java.util.ArrayList)

mx.collections.ArrayCollection

java.lang.Object[]

Array

java.util.Map

Object (untyped). For example, a java.util.Map[] is converted to an Array (of Objects).

java.util.Dictionary

Object (untyped)

org.w3c.dom.Document

XML object

null

null

java.lang.Object (other than previously listed types)

Typed Object

Objects are serialized using Java bean introspection rules and also include public fields. Fields that are static, transient, or nonpublic, as well as bean properties that are nonpublic or static, are excluded.

Note: You can enable legacy XML support for the flash.xml.XMLDocument type on any channel that is defined in the services-config.xml file. In Flex 1.5, java.util.Map was sent as an associative or ECMA Array. This is no longer a recommended practice. You can enable legacy Map support to associative Arrays, but Adobe recommends against doing this.

The following table contains type mappings between types that are specific to Apache Axis and ActionScript types for RPC-encoded web services:

Apache Axis type

ActionScript type

Map

Object

RowSet

Can only receive RowSets; can’t send them.

Document

Can only receive Documents; can’t send them.

Element

flash.xml.XMLNode

Configuring AMF serialization on a channel

You can support legacy AMF type serialization used in earlier versions of Flex and configure other serialization properties in channel definitions in the services-config.xml file.

The following table describes the properties that you can set in the <serialization> element of a channel definition:

Property

Description

enable-small-messages

Default value is true. If enabled, messages are sent using an alternative smaller form if one is available and the endpoint supports it. When you set the value of this property to false, messages include headers including messageId, timestamp, correlationId, and destination, which can be useful for debugging. When you disable small messages, enable debug logging to include messages in server logs. For information, see Server-side logging.

ignore-property-errors

Default value is true. Determines if the endpoint should throw an error when an incoming client object has unexpected properties that cannot be set on the server object.

include-read-only

Default value is false. Determines if read-only properties should be serialized back to the client.

log-property-errors

Default value is false. When true, unexpected property errors are logged.

legacy-collection

Default value is false. When true, instances of java.util.Collection are returned to the client as ActionScript Array objects instead of mx.collections.ArrayCollection objects. When true, during client to server deserialization, instances of ActionScript Array objects are deserialized into Java List objects instead of Java Object arrays.

legacy-map

Default value is false. When true, java.util.Map instances are serialized as an ECMA Array or associative array instead of an anonymous Object.

legacy-xml

Default value is false. When true, org.w3c.dom.Document instances are serialized as flash.xml.XMLDocument instances instead of intrinsic XML (E4X capable) instances.

legacy-throwable

Default value is false. When true, java.lang.Throwable instances are serialized as AMF status-info objects (instead of normal bean serialization, including read-only properties).

legacy-externalizable

Default value is false. When true, java.io.Externalizable types (that extend standard Java classes like Date, Number, String) are not serialized as custom objects (for example, MyDate is serialized as Date instead of MyDate). Note that this setting overwrites any other legacy settings. For example, if legacy-collection is true but the collection implements java.io.Externalizable, the collection is returned as custom object without taking the legacy-collection value into account.

type-marshaller

Specifies an implementation of flex.messaging.io.TypeMarshaller that translates an object into an instance of a desired class. Used when invoking a Java method or populating a Java instance and the type of the input object from deserialization (for example, an ActionScript anonymous Object is always deserialized as a java.util.HashMap) doesn't match the destination API (for example, java.util.SortedMap). Thus, the type can be marshalled into the desired type.

The flex.messaging.io.amf.translator.ASTranslator class is an implementation of TypeMarshaller that you can use as an example for your own TypeMarshaller implementations.

restore-references

Default value is false. An advanced switch to make the deserializer keep track of object references when a type translation has to be made; for example, when an anonymous Object is sent for a property of type java.util.SortedMap, the Object is first deserialized to a java.util.Map as normal, and then translated to a suitable implementation of SortedMap (such as java.util.TreeMap). If other objects pointed to the same anonymous Object in an object graph, this setting restores those references instead of creating SortedMap implementations everywhere. Notice that setting this property to true can slow down performance significantly for large amounts of data.

instantiate-types

Default value is true. Advanced switch that when set to false stops the deserializer from creating instances of strongly typed objects and instead retains the type information and deserializes the raw properties in a Map implementation, specifically flex.messaging.io.ASObject. Notice that any classes under flex.* package are always instantiated.

Using custom serialization between ActionScript and Java

If the standard mechanisms for serializing and deserializing data between ActionScript on the client and Java on the server do not meet your needs, you can write your own serialization scheme. You implement the ActionScript-based flash.utils.IExternalizable interface on the client and the corresponding Java-based java.io.Externalizable interface on the server.

A typical reason to use custom serialization is to avoid passing all of the properties of either the client-side or server-side representation of an object across the network tier. When you implement custom serialization, you can code your classes so that specific properties that are client-only or server-only are not passed over the wire. When you use the standard serialization scheme, all public properties are passed back and forth between the client and the server.

On the client side, the identity of a class that implements the flash.utils.IExternalizable interface is written in the serialization stream. The class serializes and reconstructs the state of its instances. The class implements the writeExternal() and readExternal() methods of the IExternalizable interface to get control over the contents and format of the serialization stream, but not the class name or type, for an object and its supertypes. These methods supersede the native AMF serialization behavior. These methods must be symmetrical with their remote counterpart to save the state of the class.

On the server side, a Java class that implements the java.io.Externalizable interface performs functionality that is analogous to an ActionScript class that implements the flash.utils.IExternalizable interface.

Note: You should not use types that implement the IExternalizable interface with the HTTPChannel if precise by-reference serialization is required. When you do this, references between recurring objects are lost and appear to be cloned at the endpoint.

Client-side Product class

The following example shows the complete source code for the client (ActionScript) version of a Product class that maps to a Java-based Product class on the server. The client Product class implements the IExternalizable interface and the server Product class implements the Externalizable interface.

// Product.as 
package samples.externalizable { 
 
import flash.utils.IExternalizable; 
import flash.utils.IDataInput; 
import flash.utils.IDataOutput; 
 
[RemoteClass(alias="samples.externalizable.Product")] 
public class Product implements IExternalizable { 
    public function Product(name:String=null) { 
        this.name = name; 
    } 
 
    public var id:int; 
    public var name:String; 
    public var properties:Object; 
    public var price:Number; 
 
    public function readExternal(input:IDataInput):void { 
        name = input.readObject() as String; 
        properties = input.readObject(); 
        price = input.readFloat(); 
    } 
 
    public function writeExternal(output:IDataOutput):void { 
        output.writeObject(name); 
        output.writeObject(properties); 
        output.writeFloat(price); 
    } 
} 
}

The client Product class uses two kinds of serialization. It uses the standard serialization that is compatible with the java.io.Externalizable interface and AMF 3 serialization. The following example shows the writeExternal() method of the client Product class, which uses both types of serialization:

public function writeExternal(output:IDataOutput):void { 
    output.writeObject(name); 
    output.writeObject(properties); 
    output.writeFloat(price); 
}

As the following example shows, the writeExternal() method of the server Product class is almost identical to the client version of this method:

public void writeExternal(ObjectOutput out) throws IOException { 
    out.writeObject(name); 
    out.writeObject(properties); 
    out.writeFloat(price); 
}

In the writeExternal() method of the client Product class, the flash.utils.IDataOutput.writeFloat() method is an example of standard serialization methods that meet the specifications for the Java java.io.DataInput.readFloat() methods for working with primitive types. This method sends the price property, which is a Float, to the server Product.

The examples of AMF 3 serialization in the writeExternal() method of the client Product class is the call to the flash.utils.IDataOutput.writeObject() method, which maps to the java.io.ObjectInput.readObject() method call in the server Product class readExternal() method. The flash.utils.IDataOutput.writeObject() method sends the properties property, which is an Object, and the name property, which is a String, to the server Product. This is possible because the AMFChannel endpoint has an implementation of the java.io.ObjectInput interface that expects data sent from the writeObject() method to be formatted as AMF 3.

In turn, when the readObject() method is called in the server Product's readExternal() method, it uses AMF 3 deserialization; this is why the ActionScript version of the properties value is assumed to be of type Map and name is assumed to be of type String.

Server-side Product class

The following example shows the complete source code of the server Product class:

// Product.java 
package samples.externalizable; 
 
import java.io.Externalizable; 
import java.io.IOException; 
import java.io.ObjectInput; 
import java.io.ObjectOutput; 
import java.util.Map; 
 
/** 
* This Externalizable class requires that clients sending and  
* receiving instances of this type adhere to the data format 
* required for serialization. 
*/ 
public class Product implements Externalizable { 
    private String inventoryId; 
    public String name; 
    public Map properties; 
    public float price; 
 
    public Product() 
    { 
    } 
 
        /** 
        * Local identity used to track third party inventory. This property is 
        * not sent to the client because it is server-specific. 
        * The identity must start with an 'X'. 
        */ 
        public String getInventoryId() { 
            return inventoryId; 
        } 
 
        public void setInventoryId(String inventoryId) { 
            if (inventoryId != null && inventoryId.startsWith("X")) 
            { 
                this.inventoryId = inventoryId; 
            } 
            else 
            { 
                throw new IllegalArgumentException("3rd party product 
                inventory identities must start with 'X'"); 
            } 
        } 
 
        /** 
         * Deserializes the client state of an instance of ThirdPartyProxy 
         * by reading in String for the name, a Map of properties 
         * for the description, and  
         * a floating point integer (single precision) for the price.  
         */ 
        public void readExternal(ObjectInput in) throws IOException, 
            ClassNotFoundException { 
            // Read in the server properties from the client representation. 
            name = (String)in.readObject(); 
            properties = (Map)in.readObject(); 
            price = in.readFloat(); 
            setInventoryId(lookupInventoryId(name, price)); 
        } 
        /** 
         * Serializes the server state of an instance of ThirdPartyProxy 
         * by sending a String for the name, a Map of properties 
         * String for the description, and a floating point 
         * integer (single precision) for the price. Notice that the inventory  
         * identifier is not sent to external clients. 
         */ 
        public void writeExternal(ObjectOutput out) throws IOException { 
            // Write out the client properties from the server representation 
            out.writeObject(name); 
            out.writeObject(properties); 
            out.writeFloat(price); 
        } 
         
        private static String lookupInventoryId(String name, float price) { 
            String inventoryId = "X" + name + Math.rint(price); 
            return inventoryId; 
        } 
}

The following example shows the readExternal() method of the server Product class:

public void readExternal(ObjectInput in) throws IOException, 
    ClassNotFoundException { 
    // Read in the server properties from the client representation. 
    name = (String)in.readObject(); 
    properties = (Map)in.readObject(); 
    price = in.readFloat(); 
    setInventoryId(lookupInventoryId(name, price)); 
    }

The writeExternal() method of the client Product class does not send the id property to the server during serialization because it is not useful to the server version of the Product object. Similarly, the writeExternal() method of the server Product class does not send the inventoryId property to the client because it is a server-specific property.

Notice that the names of a Product properties are not sent during serialization in either direction. Because the state of the class is fixed and manageable, the properties are sent in a well-defined order without their names, and the readExternal() method reads them in the appropriate order.

Remote object class

The following example shows the source code of the Java class, ProductRegistry, which is called with the RemoteObject component on the client:

package example.externalizable; 
import java.util.Collection; 
import java.util.Collections; 
import java.util.HashMap; 
import java.util.Map; 
/** 
 * A simple registry to manage instances of Product for an example of the 
 * Externalizable API for custom serialization. 
 */ 
public class ProductRegistry 
{ 
    public ProductRegistry() 
    { 
        registry = Collections.synchronizedMap(new HashMap()); 
        Product p = new Product(); 
        p.name = "Example Widget"; 
        p.description = "The right widget for any problem."; 
        p.price = 350; 
        registerProduct(p); 
        p = new Product(); 
        p.name = "Example Gift"; 
        p.description = "The perfect gift for any occasion."; 
        p.price = 225; 
        registerProduct(p); 
    } 
    public void registerProduct(Product product) 
    { 
        registry.put(product.getId(), product); 
    } 
    public Collection getProducts() 
    { 
        return registry.values(); 
    } 
    private Map registry; 
}

Destination configuration

The following XML snippet shows the ProductRegistry destination in the remoting-config.xml file:

<destination id="ProductRegistry"> 
    <properties> 
        <source>example.externalizable.ProductRegistry</source> 
        <scope>application</scope> 
    </properties> 
</destination>

MXML application code

The following example shows the MXML application that calls the ProductRegistry destination on the server:

<?xml version="1.0"?> 
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"> 
    <mx:Script> 
    <![CDATA[ 
        import mx.collections.ArrayCollection; 
        import mx.controls.Alert; 
        import mx.rpc.events.FaultEvent; 
        import mx.rpc.events.ResultEvent; 
        import example.externalizable.Product; 
        [Bindable] 
        public var products:ArrayCollection 
        
        // Create a compile time dependency on Product class. 
        private static var dep:Product; 
        public function resultHandler(event:ResultEvent):void 
        { 
            products = event.result as ArrayCollection; 
        } 
        public function faultHandler(event:FaultEvent):void 
        { 
             Alert.show("Fault",  event.fault.toString()); 
        } 
    ]]> 
    </mx:Script> 
    <mx:RemoteObject id="remoteObject" destination="ProductRegistry" 
        fault="faultHandler(event)" 
        result="resultHandler(event)" /> 
    <mx:Panel title="Externalizable Example" height="400" width="600" 
        paddingTop="10" paddingLeft="10" paddingRight="10"> 
        <mx:DataGrid id="grid" width="100%" rowCount="5" dataProvider="{products}"> 
            <mx:columns> 
                <mx:DataGridColumn dataField="name" headerText="Name"/> 
                <mx:DataGridColumn dataField="price" headerText="Price"/> 
            </mx:columns> 
        </mx:DataGrid> 
        <mx:Form width="100%"> 
            <mx:FormItem label="Name"> 
                <mx:Label text="{grid.selectedItem.name}"/> 
            </mx:FormItem> 
            <mx:FormItem label="Price"> 
                <mx:Label text="{grid.selectedItem.price}"/> 
            </mx:FormItem> 
            <mx:FormItem label="Description"> 
                <mx:TextArea text="{grid.selectedItem.description}"/> 
            </mx:FormItem> 
        </mx:Form> 
        <mx:Button label="Get Products" click="remoteObject.getProducts()" /> 
    </mx:Panel> 
</mx:Application>