Using RemoteObject components

You can use a Flex RemoteObject component to call methods on a ColdFusion component or Java class.

You can also use RemoteObject components with PHP and .NET objects in conjunction with third-party software, such as the open source projects AMFPHP and SabreAMF, and Midnight Coders WebORB. For more information, see the following websites:

RemoteObject components use the AMF protocol to send and receive data, while WebService and HTTPService components use the HTTP protocol. AMF is significantly faster than HTTP, however server-side coding and configuration is typically more complex.

As with HTTPService and WebService components, you can use a RemoteObject component to display the result of a database query in an application. You can also use the component to insert, update, and delete data in a database. When the result of the query has been returned to the application, you can display it in one or more user interface controls.

For API reference information about the RemoteObject component, see mx.rpc.remoting.mxml.RemoteObject.

Sample RemoteObject application

MXML code

The application in the following example uses a RemoteObject component to call a ColdFusion component. The ColdFusion component queries a MySQL database table called users. It returns the query result to the application where it is bound to the dataProvider property of a DataGrid control and displayed in the DataGrid control. The application also sends the user name and e-mail address of new users to the ColdFusion component, which performs an insert into the user database table.

<?xml version="1.0" encoding="utf-8"?> 
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
        xmlns:s="library://ns.adobe.com/flex/spark" 
        xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600"> 
    <fx:Declarations> 
        <mx:RemoteObject 
            id="userRequest" 
            destination="ColdFusion" 
            source="flexapp.returnusers"> 
             
            <mx:method name="returnRecords" result="returnHandler(event)" 
                       fault="mx.controls.Alert.show(event.fault.faultString)"/> 
            <mx:method name="insertRecord" result="insertHandler()" 
                       fault="mx.controls.Alert.show(event.fault.faultString)"/> 
        </mx:RemoteObject> 
    </fx:Declarations> 
     
    <fx:Script> 
        <![CDATA[ 
            import mx.rpc.events.ResultEvent; 
             
            private function returnHandler(e:ResultEvent):void  
            { 
                dgUserRequest.dataProvider = e.result; 
            } 
            private function insertHandler():void  
            { 
                userRequest.returnRecords(); 
            } 
            private function clickHandler():void  
            { 
                userRequest.insertRecord(username.text, emailaddress.text); 
            }  
        ]]> 
    </fx:Script> 
     
    <mx:Form x="22" y="10" width="300"> 
        <mx:FormItem> 
            <s:Label text="Username" />     
            <s:TextInput id="username"/> 
        </mx:FormItem> 
        <mx:FormItem> 
            <s:Label text="Email Address" />     
            <s:TextInput id="emailaddress"/> 
        </mx:FormItem> 
        <s:Button label="Submit" click="clickHandler()"/> 
    </mx:Form> 
 
    <mx:DataGrid id="dgUserRequest" x="22" y="200"> 
        <mx:columns> 
            <mx:DataGridColumn headerText="User ID" dataField="userid"/> 
            <mx:DataGridColumn headerText="User Name" dataField="username"/> 
        </mx:columns> 
    </mx:DataGrid> 
</s:Application>

In this application, the RemoteObject component’s destination property is set to Coldfusion and the source property is set to the fully qualified name of the ColdFusion component.

In contrast, when working with LiveCycle Data Services ES or BlazeDS, you specify a fully qualified class name in the source property of a remoting service destination in a configuration file, which by default is the remoting-config.xml file. You specify the name of the destination in the RemoteObject component’s destination property. The destination class also must have a no-args constructor. You can optionally configure a destination this way when working with ColdFusion instead of by using the source property on the RemoteObject component.

ColdFusion component

The application calls the following ColdFusion component. This ColdFusion code performs SQL database inserts and queries and returns query results to the application. The ColdFusion page uses the cfquery tag to insert data into the database and query the database, and it uses the cfreturn tag to format the query results as a ColdFusion query object.

<cfcomponent name="returnusers">  
    <cffunction name="returnRecords" access="remote" returnType="query"> 
 
        <cfquery name="alluserinfo" datasource="flexcf"> 
                SELECT userid, username, emailaddress FROM users 
        </cfquery> 
        <cfreturn alluserinfo> 
    </cffunction> 
    <cffunction name="insertRecord" access="remote" returnType="void"> 
 
        <cfargument name="username" required="true" type="string"> 
        <cfargument name="emailaddress" required="true" type="string"> 
        <cfquery name="addempinfo" datasource="flexcf"> 
            INSERT INTO users (username, emailaddress) VALUES ( 
            <cfqueryparam value="#arguments.username#" cfsqltype="CF_SQL_VARCHAR" maxlength="255">, 
            <cfqueryparam value="#arguments.emailaddress#" cfsqltype="CF_SQL_VARCHAR" maxlength="255"> ) 
        </cfquery>     
        <cfreturn> 
    </cffunction>     
</cfcomponent>

Calling RemoteObject components in ActionScript

In the following ActionScript example, calling the useRemoteObject() method declares the service, sets the destination, sets up result and fault event listeners, and calls the service’s getList() method.

<?xml version="1.0"?> 
<!-- fds\rpc\ROInAS.mxml --> 
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
        xmlns:s="library://ns.adobe.com/flex/spark" 
        xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600"> 
    <fx:Script> 
        <![CDATA[ 
            import mx.controls.Alert; 
            import mx.rpc.remoting.RemoteObject; 
            import mx.rpc.events.ResultEvent; 
            import mx.rpc.events.FaultEvent; 
 
            [Bindable] 
            public var empList:Object;          
            public var employeeRO:RemoteObject; 
 
            public function useRemoteObject(intArg:int, strArg:String):void { 
                employeeRO = new RemoteObject(); 
                employeeRO.destination = "SalaryManager"; 
                employeeRO.getList.addEventListener("result", getListResultHandler); 
                employeeRO.addEventListener("fault", faultHandler); 
                employeeRO.getList(deptComboBox.selectedItem.data); 
            } 
 
            public function getListResultHandler(event:ResultEvent):void { 
                 // Do something 
                empList=event.result; 
            } 
 
            public function faultHandler (event:FaultEvent):void { 
             // Deal with event.fault.faultString, etc. 
                Alert.show(event.fault.faultString, 'Error'); 
            } 
        ]]> 
    </fx:Script> 
    <s:ComboBox id="deptComboBox"/> 
</mx:Application>

Accessing Java objects in the source path

The RemoteObject component lets you access stateless and stateful Java objects that are in the LiveCycle Data Services ES, BlazeDS, or ColdFusion web application's source path. You can place stand-alone class files in the web application's WEB‑INF/classes directory to add them to the source path. You can place classes contained in Java Archive (JAR) files in the web application's WEB‑INF/lib directory to add them to the source path. You specify the fully qualified class name in the source property of a remoting service destination in the LiveCycle Data Services ES, BlazeDS, or ColdFusion services-config.xml file, or a file that it includes by reference, such as the remoting-config.xml file. The class also must have a no-args constructor. For ColdFusion, you can optionally set the RemoteObject component’s destination property to Coldfusion and the source property to the fully qualified name of a ColdFusion component or Java class.

When you configure a remoting service destination to access stateless objects (the request scope), Flex creates a new object for each method call instead of calling methods on the same object. You can set the scope of an object to the request scope (default value), the application scope, or the session scope. Objects in the application scope are available to the web application that contains the object. Objects in the session scope are available to the entire client session.

When you configure a remote object destination to access stateful objects, Flex creates the object once on the server and maintains state between method calls. If storing the object in the application or session scope causes memory problems, you should use the request scope.

Accessing EJBs and other objects in JNDI

You can access Enterprise JavaBeans (EJBs) and other objects stored in the Java Naming and Directory Interface (JNDI) by calling methods on a destination that is a service facade class that looks up an object in JNDI and calls its methods.

You can use stateless or stateful objects to call the methods of Enterprise JavaBeans and other objects that use JNDI. For an EJB, you can call a service facade class that returns the EJB object from JNDI and calls a method on the EJB.

In your Java class, you use the standard Java coding pattern, in which you create an initial context and perform a JNDI lookup. For an EJB, you also use the standard coding pattern in which your class contains methods that call the EJB home object's create() method and the resulting EJB’s business methods.

The following example uses a method called getHelloData() on a facade class destination:

<mx:RemoteObject id="Hello" destination="roDest"> 
    <mx:method name="getHelloData"/> 
</mx:RemoteObject>

On the Java side, the getHelloData() method could encapsulate everything necessary to call a business method on an EJB. The Java method in the following example performs the following actions:

  • Creates new initial context for calling the EJB

  • Performs a JNDI lookup that gets an EJB home object

  • Calls the EJB home object's create() method

  • Calls the EJB’s sayHello() method

    ... 
    public void getHelloData() { 
        try{ 
            InitialContext ctx = new InitialContext(); 
            Object obj = ctx.lookup("/Hello"); 
            HelloHome ejbHome = (HelloHome) 
            PortableRemoteObject.narrow(obj, HelloHome.class); 
            HelloObject ejbObject = ejbHome.create(); 
            String message = ejbObject.sayHello(); 
            } 
        catch (Exception e); 
        } 
    ...

Reserved method names

The Flex remoting library uses the following method names; do not use these as your own method names:

addHeader() 
addProperty() 
deleteHeader() 
hasOwnProperty() 
isPropertyEnumerable() 
isPrototypeOf() 
registerClass() 
toLocaleString() 
toString() 
unwatch() 
valueOf() 
watch()

Also, you should not begin method names with an underscore (_) character.

RemoteObject methods (operations) are usually accessible by simply naming them after the service variable. However, naming conflicts can occur if an operation name happens to match a defined method on the service. You can use the following method in ActionScript on a RemoteObject component to return the operation of the given name:

public function getOperation(name:String):Operation

Serializing between ActionScript and Java

LiveCycle Data Services ES and BlazeDS serialize data between ActionScript (AMF 3) and Java and ColdFusion data types in both directions. For information about ColdFusion data types, see the ColdFusion documentation set.

Converting data from ActionScript to Java

When method parameters send data from an application to a Java object, the data is automatically converted from an ActionScript data type to a Java data type. When LiveCycle Data Services ES or BlazeDS 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 match exactly 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 indexes 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 indexes are consecutive, with no gap, starting from 0 (zero). A sparse array is one in which there are gaps between the numeric indexes; the array is treated like an object and the numeric indexes 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[], 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 important only 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 ES and BlazeDS handle 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 that 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 ES and BlazeDS also convert 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 ES and BlazeDS convert 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 ES and BlazeDS pass an instance of java.util.TreeSet to parameters typed with the java.util.SortedSet interface.

LiveCycle Data Services ES and BlazeDS pass 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 to be 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 ES and BlazeDS do not handle implicitly, values found in public bean properties with get/set methods and public variables are sent to the client as properties on an 0bject. Private properties, constants, static properties, read-only properties, and so on are not serialized. For ActionScript objects, public properties defined with the get/set accessors and public variables are sent to the server.

LiveCycle Data Services ES and BlazeDS use the standard Java class, java.beans.Introspector, to get property descriptors for a JavaBean 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 examples 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.

Converting data from Java to ActionScript

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

Java type

ActionScript type (AMF 3)

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 by using JavaBean 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.

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 you can set in the <serialization> element of a channel definition:

Property

Description

<ignore-property-errors>    true</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.

<log-property-errors>    false</log-property-errors>

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

<legacy-collection>false</legacy-collection>

Default value is false. When true, instances of java.util.Collection are returned as ActionScript arrays. When false, instances of java.util.Collection are returned as mx.collections.ArrayCollection.

<legacy-map>false</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>false</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>false</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).

<type-marshaller>    className</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.

<restore-references>    false</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>    true</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

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 class's state.

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: If precise by-reference serialization is required, you should not use types that implement the IExternalizable interface with the HTTPChannel. When you do this, references between recurring objects are lost and appear to be cloned at the endpoint.

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 implements the IExternalizable interface, and the server Product 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 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, 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 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 client Product’s writeExternal() method, 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 example of AMF 3 serialization in the client Product’s writeExternal() method is the call to the flash.utils.IDataOutput.writeObject() method, which maps to the java.io.ObjectInput.readObject() method call in the server Product’s 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.

The following example shows the complete source 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 server Product’s readExternal() method:

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 client Product’s writeExternal() method 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 server Product’s writeExternal() method does not send the inventoryId property to the client because it is a server-specific property.

Notice that the names of a Product’s 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.