Package com.carbonfive.flash

ASTranslator provides JavaBean-style mapping between Java objects in any J2EE application server and ActionScript objects in Flash MX when used with Macromedia Flash Remoting MX.

See:
          Description

Class Summary
ASTranslator ASTranslator provides the ability to translate between ASObjects used by Macromedia Flash Remoting and Java objects in your application.
ASTranslatorFactory  
CachingManager  
Context Context passed to encoders and decoders, as well as factories, during translation to and from Action Script.
IdentityMap A lightweight HashMap (not all methods implemented) that wraps keys with an object that uses equivalence (==) for equals() and hashCode().
LoopFinder LoopFinder detects infinite loops during translation to Action Script.
ReferenceCache Cache used to maintain references between ASObject graphs as well as Java object graphs.
TranslationFilter TranslationFilter holds classes and properties that should be ignored during a translation to Action Script.
 

Exception Summary
ASTranslationException  
InfiniteLoopException Thrown when an infinite loop is detected by LoopFinder.
 

Package com.carbonfive.flash Description

ASTranslator provides JavaBean-style mapping between Java objects in any J2EE application server and ActionScript objects in Flash MX when used with Macromedia Flash Remoting MX.

ASTranslator For Macromedia Flash Remoting MX

ASTranslator is designed to be used with Macromedia Flash Remoting MX on any J2EE application server. It provides JavaBean-style mapping between Java objects in the application server and ActionScript objects in Flash MX. More information on why this is so important, read the overview.

ASTranslator is supported by Carbon Five and can be deployed on application servers with Flash Remoting MX including Macromedia JRun, Jakarta Tomcat, JBoss, Caucho Resin, IBM WebSphere, BEA Weblogic and ATG Dynamo.


Overview

ASTranslator provides the ability to translate between ASObjects used by Macromedia Flash Remoting and Java objects in your application. ASObjects are essentially HashMaps where the key values are case insensitive and with an additional field "type". ASObjects are a Java representation of ActionScript objects in Flash.

We created ASTranslator because of an issue we found with Flash Remoting MX for J2EE. When returning Java objects from service calls to Flash, Flash Remoting serializes Java objects according to their private member state, not using it's publicly declared property accessors as you would expect. So if you have a Java class:

package com.server;

public class Player
{
  private String _name;
  
  public getName()
  {
    return _name;
  }
}
and a service class:
package com.server;

public class Service
{
  public Player getPlayer(String name)
  {
    Player player = Roster.getPlayer(name);
    return player;
  }
}
calling Service.getPlayer() from Flash MX would return an ActionScript object with a property "_name" instead of "name." This removes one of the primary values of object oriented programming: encapsulation. ActionScript programmers in Flash can see the internal state of Java objects on the server that even other Java objects running on the server cannot see. This is bad. It can be really bad (both for performance and security) if the internal state includes a lot more information than the object was designed to expose through its public interface. ASTranslator adds the behavior of using JavaBean-style introspection to serialize Java objects for Remoting. To use ASTranslator in the above example, we would change the Service class as follows:
package com.server;

import flashgateway.io.ASObject;
import com.carbonfive.flash.ASTranslator;
  
public class Service
{
  public ASObject getPlayer(String name)
  {
    Player player = Roster.getPlayer(name);
    ASObject aso = (ASObject) new ASTranslator().toActionScript(player);
    return aso;
  }
}
Flash will now receive an ActionScript object with the property "name."

Features

JavaBean-Style Serialization From Java to ActionScript

As described in the overview, ASTranslator enhances Flash Remoting by adding the ability to send Java objects to Flash using the Java object properties as defined by the object's property accessors (getters and setters) rather than using the object's internal member state as Remoting does by default.

ASTranslator follows the conversion rules for Flash Remoting for J2EE and relies on the remoting gateway to handle conversion of class types named by the conversion rules. Additionally, if it does not know what to do with an object type, it simply passes it through. Therefore it is safe to pass all objects through ASTranslator.toActionScript() when sending them back to Flash. This is the recommended use.

The Java programmer can identify classes, interfaces or specific properties of objects that should not be translated to ActionScript in a call to ASTranslator.toActionScript(). This is done through the use of ASTranslator.ignoreClass() and ASTranslator.ignoreProperty(). This is useful when you wish to reduce network traffic by not sending irrelevant data to Flash. Specific information on this is provided in the usage documentation.

ASTranslator also identifies potential loops while translating from a JavaBean to an ActionScript object.

Additionally, if the ActionScript programmer has registered ActionScript objects using Object.registerClass() that map directly to Java objects, the objects returned as the result of a Remoting service call will be of that registered type when returned to the service XXXX_onResult() callback method. More information on this is provided in the usage documentation.

JavaBean-Style Deserialization From ActionScript to Java

When sending ActionScript objects to the server through Flash Remoting service method calls, the Java service class will either receive one of the known Java object types or an ASObject if the object type is not known. The process of converting these ASObjects to model objects in the application server can be tedious and brittle. ASTranslator adds automatic conversion of ASObjects to Java objects using JavaBean-style introspection to set the Java object properties.

With Flash Remoting alone, calling a service method with an object argument from Flash would look like:

#include "NetServices.as"

NetServices.setDefaultGatewayUrl("http://localhost/gateway");
var gatewayConnection = NetServices.createGatewayConnection();
var service = gatewayConnection.getService("Service", this);

Player = function() {}

var player = new Player();
player.name = "Joe";

service.savePlayer(player);
and the Java service class would look like:
package com.server;

import flashgateway.io.ASObject;
  
public class Service
{
  public void savePlayer(ASObject asPlayer)
  {
    Player player = new Player();
    player.setName((String) asPlayer.get("name"));
    
    Roster.savePlayer(player);
  }
}
With ASTranslator, the ActionScript would look like:
#include "NetServices.as"

NetServices.setDefaultGatewayUrl("http://localhost/gateway");
var gatewayConnection = NetServices.createGatewayConnection();
var service = gatewayConnection.getService("Service", this);

Player = function() {}
Object.registerClass("com.server.Player", Player);

var player = new Player();
player.name = "Joe";

service.savePlayer(player);
and the service class would look like:
import flashgateway.io.ASObject;
import com.carbonfive.flash.ASTranslator;
  
public class Service
{
  public void savePlayer(ASObject asPlayer)
  {
    Player player = (Player) new ASTranslator().fromActionScript(asPlayer);
    
    Roster.savePlayer(player);
  }
}
ASTranslator will create and set as many properties in the Java Player object as match in the ASObject received from Flash.

In order for ASTranslator to know what Java object type to convert the ASObject to, the type field of the ASObject must be set to the class name of the desired Java object type. This is achieved by using Object.registerClass() in ActionScript to associate the ActionScript object type with the Java class. More information on this is provided in the usage documentation.

Download

The latest release of ASTranslator is available for download from SourceForge.net. For the adventurous, you can check out the latest changes from CVS.

Prerequisites

To use ASTranslator effectively, you must first have Flash Remoting for J2EE installed in your application server and the Flash Remoting Components installed in your Flash MX authoring environment.

While Macromedia is somewhat vague on the issue we strongly recommend installing Flash Remoting for your application by:

  1. locating flashgateway.jar in the flashgateway.ear or flashgateway.war file installed by the Macromedia installer
  2. placing it in your web application's WEB-INF/lib/ directory
  3. mapping the gateway for your web application by adding the following to your web application's WEB-INF/web.xml file:
      <servlet>
        <servlet-name>FlashGatewayServlet</servlet-name>
        <display-name>Flash Remoting Servlet</display-name>
        <description>Servlet-based plugin to Flash Remoting</description>
        <servlet-class>flashgateway.controller.GatewayServlet</servlet-class>
        <load-on-startup>10</load-on-startup>
      </servlet>
    
      <servlet-mapping>
        <servlet-name>FlashGatewayServlet</servlet-name>
        <url-pattern>/gateway</url-pattern>
      </servlet-mapping>
    
This will make the Remoting Gateway Service available at http://www.yourdomain.com/yourwebapp/gateway and all classes in your application visible to Flash Remoting.

ASTranslator also utilizes specific components from the Jakarta Commons project. These libraries are provided in the distribution, and can be freely used by commercial and non-commercial applications. More information can be found here:

Install

To install ASTranslator, first install Flash Remoting for J2EE MX as recommended in prerequisites. You must then unzip astranslator-x.x.zip and copy the following jars to your web application's WEB-INF/lib/ directory:

If you have installed Flash Remoting MX for J2EE in another manner, the best location for astranslator-x.x.jar will depend on the application server you are using. Here you get in to the sticky world of class loader precedence. Better to keep it all in your webapp.

Usage

ASTranslator is the only class you will use in Java.

From Flash to Java

Use ASTranslator.fromActionScript() to translate ASObjects sent to remoting service methods from Flash.
import flashgateway.io.ASObject;
import com.carbonfive.flash.ASTranslator;
  
public class Service
{
  public void savePlayer(ASObject asPlayer)
  {
    Player player = (Player) new ASTranslator().fromActionScript(asPlayer);
    
    Roster.savePlayer(player);
  }
}
Note that you must cast the result of this method call to the expected return type. fromActionScript() can take any object as an argument. It will translate any object and it's composite objects as deep as possible.

In order for ASTranslator to know what Java class an ASObject should become, you must register your ActionScript objects in Flash MX with the Java class name that the ActionScript object maps to.

Player = function() {}                              // Object definition
Object.registerClass("com.server.Player", Player);  // Player maps to com.server.Player in Java

From Java to Flash

Use ASTranslator.toActionScript() to translate Java objects to ASObjects before sending them back to flash as the result of a Flash Remoting service call.
package com.server;

import flashgateway.io.ASObject;
import com.carbonfive.flash.ASTranslator;
  
public class Service
{
  public ASObject getPlayer(String name)
  {
    Player player = Roster.getPlayer(name);
    ASObject aso = (ASObject) new ASTranslator().toActionScript(player);
    return aso;
  }
}
Be aware that your custom Java object, eg: Player, must implement Serializable in order for ASTranslator to property convert them to Action Script objects.

Note that you must cast the result of this method call to the expected return type. It will either be an ASObject, a Java Collection or one of the Java Class types known to Flash Remoting (XML Document, ResultSet,...).

If you wish to specify classes or interfaces to be left out of the translation, the code can look like this:

package com.server;

import flashgateway.io.ASObject;
import com.carbonfive.flash.ASTranslator;
  
public class Service
{
  public ASObject getPlayer(String name)
  {
    Player player = Roster.getPlayer(name);
    ASTranslator translator = new ASTranslator();
    translator.ignoreClass("com.yourcompany.Address");
    translator.ignoreProperty("com.yourcompany.Player", "email");

    ASObject aso = (ASObject) translator.toActionScript(player);
    return aso;
  }
}
This would cause ASTranslator to ignore any object of type com.yourcompany.Address while translating, as well as the getEmail() property of the com.yourcompany.Player class itself.

For Java object that are not known to Flash Remoting, ASTranslator will set the "type" field of the resulting ASObject to be the class name of the translated Java object. If you register your ActionScript objects to map to Java objects as detailed in the section above, the ActionScript objects that are returned to Flash will be of the correct ActionScript object type.

#include "NetServices.as"

NetServices.setDefaultGatewayUrl("http://localhost/gateway");
var gatewayConnection = NetServices.createGatewayConnection();
var service = gatewayConnection.getService("Service", this);

// Define Player
Player = function() {}

// Define Player methods
Player.prototype.getName = function()
{
  return name;
}

// Register Player object to map to com.server.Player Java object
Object.registerClass("com.server.Player", Player);

// Get Player from Flash Remoting
service.getPlayer("Joe");

// Handle Flash Remoting result
getPlayer_Result(player)
{
  // player is an ActionScript object of type Player
  var name = player.getName();
}

Recommended Usage

Flash Remoting MX provides features that we feel confuse it's best use. Yes, you can communicate directly with Session and Entity EJBs, JSPs and Servlets, and have object state persisted in the user session but just because you can does not mean you should. Here are our recommendations for using Flash Remoting to build easy to integrate and scalable Rich Internet Applications: Using Flash Remoting in this manner to support a Service Oriented Architecture is a good idea. It creates a well-defined point of integration between the Flash client application and the J2EE server application. It exposes your J2EE application a set of services that can easily be made available to other client platforms through Web Services or to a local JSP/Servlets implementation.

Error Handling

In a Flash MX client to a Remoting service, you handle the results of a remoting call (e.g. service.doCall()) by implementing a doCall_onResult callback method for service calls that execute successfully and an doCall_onStatus callback method for exceptions thrown by the Remoting gateway. The Remoting gateway will throw exceptions when: Given this behavior and our recommendation to use a service-oriented architecture with Flash Remoting, we recommend having the service implementation throw exceptions to be handled by the Flash MX client. In the simplest case we recommend having all service methods simply throw Exception.
package com.server;

import flashgateway.io.ASObject;
import com.carbonfive.flash.ASTranslator;
  
public class Service
{
  public ASObject getPlayer(String name)
    throws Exception
  {
    Player player = Roster.getPlayer(name);
    ASObject aso = (ASObject) new ASTranslator().toActionScript(player);
    return aso;
  }
}
This would allow the Flash MX client to inspect all exceptions thrown during the service invocation and determine what to do about it at runtime. This strategy is discussed in the Flash Remoting documentation at http://livedocs.macromedia.com/frdocs/Using_Flash_Remoting_MX/UseActionScript8.jsp.

The "type" field of the error object returned to getPlayer_onStatus should provide the class name of the thrown exception allowing us to implement the callback in Flash as:

getPlayer_onStatus = function (error)
{
  if (error.type == "com.server.PlayerNotFoundException")
  {
    trace("unable to find the player");
  }
  else
  {
    trace("an unknown error occurred");
  }
}
One limitation to this approach is that inheritance is not taken in to account. A thrown exception that extends com.server.PlayerNotFoundException is a com.server.PlayerNotFoundException but would not be handled as one in the above ActionScript code.

A more significant limitation to this approach is that Flash Remoting MX for J2EE does not actually send exceptions thrown by a service class to the XXX_onStatus callback method. Because the Flash Remoting gateway uses introspection to invoke the service methods, all exceptions thrown by service calls are nested within a java.lang.reflect.InvocationTargetException. Tracing the properties of an exception thrown by a service call results in output that looks something like:

 code:        SERVER.PROCESSING
 level:       error
 description: Service threw an exception during method invocation: null
 type: java.lang.reflect.InvocationTargetException
 rootcause: 
 details:     java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        ...
        at flashgateway.Gateway.invoke(Gateway.java:184)
        ...
        at java.lang.Thread.run(Thread.java:536)
Caused by: com.server.PlayerNotFoundException: There is no player with name 'bob'
        at com.server.Roster.getPlayer(Roster.java:45)
        ...
We would like the nested com.server.PlayerNotFoundException to be used as exception upon which the error object is based but it is not. We have not come up with a great solution to this problem.

The best solution we have come up with is to build a new error object that inspects the "detail" field of the error returned by Remoting to determine the correct values for "description," "type" and "details." Unfortunately the formatting of the stack trace as provided by the "details" field differs depending on the JVM version being used on the server. Specifically, between Java 1.3 and Java 1.4 the way in which nested exceptions are printed in a stack trace is different. This makes inspecting the "detail" field brittle and a source of hard-to-uncover bugs.

Please let us know if you have recommendations for dealing with this issue.

Data Binding

There's been some discussion of binding more than just RecordSets to UI components using DataGlue. This makes a ton of sense, especially since we typically think of collections of business objects instead of database recordsets - a mindset that ASTranslator is designed for. Here's a simple start that binds any array of objects to a UI component using DataGlue. A handy use of this would be to take an array result received from remoting and bind it to a UI component just as you would a RecordSet.

Here's an implementation of an ArrayDataProvider that provides a DataProvider view of an array. You can use objects of this class to bind to UI components with DataGlue.

/**
 * ArrayDataProvider.as
 * Alon Salant, Carbon Five
 *
 * ArrayDataProvider implements a DataProvider view of an array
 * so that it can be bound to UI components using DataGlue.
 */
 
#include "RsDataProviderClass.as"

_global.ArrayDataProvider = function(list)
{
  this.init();
  this.addAll(list);
}

ArrayDataProvider.prototype = new RsDataProviderClass();

ArrayDataProvider.prototype.addAll = function(list)
{
  if (list != null && list.length > 0)
  {
    for (var i = 0; i < list.length; i++)
    {
        this.addItem(list[i]);  
    }
  }
}

ArrayDataProvider.prototype.checkLocal = function()
{
  return true;
}
Here's the code from a sample fla that has a list box UI component with instance name lb_people.
#include "DataGlue.as"
#include "ArrayDataProvider.as"

Person = function(firstName, lastName)
{
  this.firstName = firstName;
  this.lastName = lastName;
}

var people = new Array();
people.push(new Person("Mike", "Wynholds"));
people.push(new Person("Sam", "Borgenson"));
people.push(new Person("Don", "Thompson"));
people.push(new Person("Alon", "Salant"));

var dataprovider = new ArrayDataProvider(people);

DataGlue.BindFormatStrings(lb_people, dataprovider, "#firstName# #lastName#", "#firstName#-#lastName#");
The last two lines you would put in a remoting XXX_Result method to bind remoting results to a UI component.

Known Issues

In our work with Flash Remoting MX for J2EE we have encountered the following issues:

Remoting Exposes Private State of Server-side Objects

This issue is discussed in the Overview and was the primary motivation for developing ASTranslator.

Exceptions Hidden by java.lang.reflect.InvocationTargetException

Exceptions thrown by the J2EE implementation of a Remoting service are nested by java.lang.reflect.InvocationTargetException and are not exposed to the Flash MX client in a usable way. This is discussed in Error Handling.

Support

For support, please read and post to the forums on SourceForge.net.

Developers

For those interesting in peeking under the hood or making changes, the distribution includes the source code, an Ant build script and JUnit unit tests that can be used to make changes. Simply unzip/unjar the distribution to get started.

Reference

Credits

Alon J Salant, Carbon Five
Mike Wynholds, Carbon Five
Steven Webster, iteration::two