The AMF protocol is used by LCDS/Blaze DS, Shared Objects and ByteArrays. But when serializing/deserializing, you loose your object type.
Use the [RemoteClass] metadata tag
A. LCDS and BlazeDS: Remote Objects
By default, when you send your ActionScript objects to the server, they are mapped to a Java Map object. When you send Jave objects to the Flex client, they are mapped to the default Object type. If you wish to retain the Java type of your objects and to map your ActionScript classes to your
Java classes, use the [RemoteClass] metadata tag with the alias attribute. In that case, you can use intellisense while programming with ActionScript and send Java POJO objects to the server rather than Java Map objects.
Let's take an e-commerce application as an example. When the user logs in, his/her cart has to be retrieved from the server. Likewise, when the user submits an order, you have to send the cart to the server in order to communicate what items he/she has selected for purchase.
The cart as well as each product in the catalog are represented and transfered using a Data Transfer Object (DTO): ShoppingCartDTO and ProductDTO respectively. These DTOs exist both in ActionScript on the client and Java on the back end. The ActionScript and Java versions have the exact same
fields.
The way to instruct Flex that your ActionScript type corresponds to the corresponding Java type is to use the [RemoteClass] metadata tag with the alias attribute, just before your class definition. The value of the alias attribute is the fully qualified name of the corresponding Java class.
package com.mycompany.core.dto
{
[Bindable]
[RemoteClass(alias="com.mycompany.core.dto.ProductDTO")]
public class ProductDTO
{
public var itemNumber:String;
public var price:Number;
public var currencyId:String;
public function ProductDTO(
itemNumber:String = null,
price:Number = 0,
currencyId:String = null
)
{
this.itemNumber = itemNumber
this.price = price;
this.currencyId = currencyId;
}
}
}
package com.mycompany.core.dto
{
[Bindable]
[RemoteClass(alias="com.mycompany.core.dto.ShoppingCartDTO")]
public class ShoppingCartDTO
{
public var products:ArrayCollection; // An ArrayCollection of ProductDTO objects
public var orderTotal:Number;
public var currencyId:String;
public function ShoppingCartDTO(
products:ArrayCollection = null,
orderTotal:Number = 0,
currencyId:String = null
)
{
this.products = products;
this.orderTotal = orderTotal;
this.currencyId = currencyId;
}
}
}
But using the [RemoteClass] metadata tag is not enough. You have to cast the result you get from the server in order for Flex to be able to make the link between the Java class and the corresponding ActionScript class.
E.g: this would be the onResult event handler of the GetShoppingCartCommand:
public function onResult( event : * = null ) : void
{
var result:Object = (event as ResultEvent).result as Object ;
if (result)
{
/** Update the Shopping Cart in the model */
this.model.shoppingCart = ShoppingCartDTO(result);
}
}
If you get an array collection of DTOs from the server, as opposed to a single DTO, then run through the Array Collection and cast each object to the ActionScript DTO type corresponding to the Java DTO type. Some will argue that this is not needed. Indeed, you can see the correct mapping
type of the objects in the debugger when you receive them from the server but they still need to be cast afterwards if you want to access their properties and to ease debugging. It is recommended by Adobe to cast the objects when you receive them from the server. Also, note that while this is not
absolutely needed with Remote Object, it is mandatory with Data Management Service, otherwise Flex will not know the mapping type of objects in the ArrayCollection.
E.g: this would be the onResult event handler of the GetProductsCatalogCommand:
public function onResult( event : * = null ) : void
{
var result:ArrayCollection = (event as ResultEvent).result as ArrayCollection;
if (result)
{
// ---------------------------------
/** Array collection for all data */
// ---------------------------------
/** Intermediate array collection */
var items:ArrayCollection = new ArrayCollection();
for each(var item:Object in result)
{
/** Add the current item to the intermediate array collection */
items.addItem(ProductDTO(item));
}
/** Update the Product array collection in the model */
this.model.products = items
}
}
If you omit the [RemoteClass] metadata tag in the DTO class, a runtime error will be triggered by Flash Player when deserializing the object and casting it to the ActionScript type: 1034 Type Coercion failed. This is because Flex knows the original Java type of the Object
and expects an ActionScript type that explicitly maps to this type.
Also, make sure you give default values to the parameters of the constructor of your DTOs. Otherwise, you will get a runtime error.
When it comes to sending DTOs as the parameters of a method call to a RemoteObject on the server, the only thing you have to do is to ensure that your DTOs are of the correct type and have the [RemoteClass] metadata tag with the correct value for the alias attribute. For example, you need to
send back the ShoppingCartDTO when the user proceeds to check out.
This would be the checkout() method of the CheckOutDelegate:
public function checkOut(username:String, shoppingCartDTO:ShoppingCartDTO) : void
{
var token : AsyncToken = service.checkOut(username, shoppingCartDTO);
token.resultHandler = responder.onResult;
token.faultHandler = responder.onFault;
}
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.
B. LCDS: Data Management Services
When you set up LCDS to use the Data Services with the Java adapter, either using the Hibernate assembler or a custom assembler, you can use [RemoteClass] with an alias to map the ActionScript object to its Java counterpart.
The only difference in your DTO is that the [Bindable] metadata tag is replaced with [Managed]. The [Managed] tag is an extension of [Bindable].
package com.mycompany.core.dto
{
[Managed]
[RemoteClass(alias="com.mycompany.core.dto.ProductDTO")]
public class ProductDTO
{
public var itemNumber:String;
public var price:Number;
public var currencyId:String;
public function ProductDTO(
itemNumber:String = null,
price:Number = 0,
currencyId:String = null
)
{
this.itemNumber = itemNumber
this.price = price;
this.currencyId = currencyId;
}
}
}
package com.mycompany.core.dto
{
[Managed]
[RemoteClass(alias="com.mycompany.core.dto.ShoppingCartDTO")]
public class ShoppingCartDTO
{
public var products:ArrayCollection; // An ArrayCollection of ProductDTO objects
public var orderTotal:Number;
public var currencyId:String;
public function ShoppingCartDTO(
products:ArrayCollection = null,
orderTotal:Number = 0,
currencyId:String = null
)
{
this.products = products;
this.orderTotal = orderTotal;
this.currencyId = currencyId;
}
}
}
C. Shared Objects. Example: saving user's preferences
SharedObjects too use AMF to serialize/deserialize objects.
But by default, when your preferences object is serialized, it looses its type:
private var sharedObject:SharedObject = SharedObject.getLocal("mySharedObject");
sharedObject.data.preferences = this.modelLocatorVO.user.preferences;
sharedObject.flush();
Next time you read your preferences object from your shared object, it will be of type Object.
this.modelLocatorVO.user.preferences = sharedObject.data.preferences; // of type Object
Thanks to the [RemoteClass] metadata tag (without alias attribute), the type of your object will be retained. Just place the metadata tag before your class declaration.
Next time you retrieve the preferences, you will see your type has been retained
this.modelLocatorVO.user.preferences = sharedObject.data.preferences; // of type UserPreferences
package com.mycompany.core.model
{
// The Remote Class metadata tag ensures the type of the object
// will be retained when serializing/deserializing to/from the Shared Object
// >> Create an instance of this class in the User instance of the ModelLocator
[Bindable]
[RemoteClass]
public class UserPreferences
{
// The current locale
public var locale:String = "en_US";
// Date separator
public var dateSeparator:String = "/";
// Skins
public var skinVersion:String = "Alternate";
// The last selected currency
private var _currency:String = "USD";
// Property you do not want to be serialized
[Transient]
private var dummy;
// ********************************************************************* /
// Getters/Setters
// ********************************************************************* /
public function get currency():String
{
return this._currency;
}
public function set currency(value:String):void
{
this._currency = value;
}
// You could/should define a getter/setter for each field...
}
}
D. ByteArray
You can also use the [RemoteClass] metadata tag when writing an object to a ByteArray the same way as with SharedObjects.