The Flash Remoting gateway translates ActionScript objects to Java objects when passing ActionScript objects as method parameters for Remoting service method calls. When it gets the results of the service call, the gateway translates the Java object results to ActionScript objects for Flash. It does these translations regardless of the service type and according to the mappings listed in Tables Table A-3 and Table A-4 in Appendix A.
There are specialized behaviors in these translations that are worth describing here.
The Remoting gateway converts objects from ActionScript to Java arbitrarily deep. This means that a graph of nested objects in ActionScript will become a graph of nested objects in Java, converted according to the mappings listed in Table A-3. Because the gateway traverses nested ActionScript objects to do the conversion, it is possible to cause a java.lang.StackOverflowException in the gateway by passing objects with recursive references. This example shows one way to get into trouble:
var service = gatewayConnection.getService( "com.oreilly.frdg.java.service.JavaClassService", this); // Create two objects that reference each other. var top = new Object( ); var bottom = new Object( ); top.bottom = bottom; bottom.top = top; // Creates a stack overflow because of infinite recursion // when the gateway tries to convert the objects to Java service.echo(top);
While the preceding example is oversimplified, it is not hard to create recursive references in your own applications (intentionally or otherwise). Be sure not to use objects with recursive references as arguments to Remoting service methods.
The gateway preserves references even when converting nested data structures. So, an ActionScript array of two references to the same ActionScript object will become a Java List of two references to the same object. For example:
var service = gatewayConnection.getService( "com.oreilly.frdg.java.service.JavaClassService", this); // Create an array of two references to the same object var obj = new Object( ); var refs = new Array(obj, obj); // Ask the service if the objects on the server are equivalent service.checkReferences(refs); // The server returns true function checkReferences_Result (result) { trace("checkReferences_Result:"); trace(" Should be true: " + result); }
The following service implementation compares the two objects in the List for referential equality and returns the result:
public class JavaClassService { public boolean checkReferences (List objs) { return objs.get(0) == objs.get(1); } }
In Table A-3, note that ActionScript objects that are not one of the known types are converted to a Java object of type flashgateway.io.ASObject. An ASObject is derived from java.util.Map with case-insensitive keys and with an additional field named type. Its interface is shown here:
package flashgateway.io; public class ASObject extends flashgateway.util.CaseInsensitiveMap { public ASObject( ); public ASObject(String type); public String getType( ); public void setType(String type); }
The Remoting gateway sets the type property of an ASObject to the key used when registering an ActionScript object in Flash using Object.registerClass(key, class), as discussed in Section 4.7.2.
Since the Remoting gateway converts ActionScript objects to only the Java objects listed in Table A-3, it cannot call service methods that take parameters that are not in the list. A Flash client calling a service method updateUser(User user) written as:
public class UserService { public void updateUser (User user) { Directory.updateUser(user); } }
with client-side ActionScript code written as:
var service = gatewayConnection.getService( "com.oreilly.frdg.java.service.UserService", this); // Create an object with a username property var user = new Object( ); user.username = "Flash User"; // Send the object to the service's updateUser( ) method service.updateUser(user);
will cause a Flash Remoting error similar to: "Service com.oreilly.frdg.java.service.JavaClassService does not have a method `updateUser' that matches the name and parameters provided."
This problem is that UserService.updateUser( ) expects a User object, but Flash passes an ASObject. The type mismatch results in an error.
In order for it be invoked by the Remoting gateway, we must write the service as follows (changes are shown in bold):
import flashgateway.io.ASObject; public class UserService { public void updateUser (ASObject asUser) { // Create a User object from the ASObject String username = (String) asUser.get("username"); User user = new User(username); Directory.updateUser(user); } }
This example changes the UserService.updateUser( ) method to accept an ASObject instead of a User object. It creates a User object from the properties of the ASObject so that it can use the application's Directory.updateUser( ) implementation.
After the Remoting gateway receives the results of calling a service method, it converts the result object for sending back to Flash, according to the conversions listed in Table A-4.
As with ActionScript-to-Java conversion, the conversion is deep in that it accesses nested elements; however, references are not preserved. The following example shows a method, getTwoOfTheSame( ), that returns a List of two references to the object passed in. We'll invoke this service from Flash to see if the references point to the same object when they are passed back to the responder function, getTwoOfTheSame_Result( ).
public class JavaClassService { public List getTwoOfTheSame (Object obj) { // Create a List with two references to the same object List list = new ArrayList( ); list.add(obj); list.add(obj); return list; } }
The following ActionScript shows a Flash client calling getTwoOfTheSame( ) and checking whether the two objects returned are references to the same object:
var service = gatewayConnection.getService( "com.oreilly.frdg.java.service.JavaClassService", this); service.getTwoOfTheSame(new Date( )); function getTwoOfTheSame_Result (result) { trace("Are they equal? " + (result[0] == result[1])); }
This ActionScript code traces:
Are they equal? false
Table A-4 indicates that the Remoting gateway converts a java.sql.ResultSet returned from a service method call to an ActionScript RecordSet object. However, you should not return a ResultSet that you have received as the result of a direct JDBC call. Such ResultSets are connected, meaning that they are backed by an open database connection. If you close the Connection object and close the live ResultSet before returning it, the gateway cannot convert it. If you do not close the ResultSet so that the gateway can do the conversion, you will quickly run into issues in the server-side application, caused by a lack of available database connections or unpredictable behavior in the ResultSet object itself.
The Flash Remoting Updater release notes mention this issue at http://www.macromedia.com/support/flash_remoting/releasenotes/mx/releasenotes.html:
Do not serialize a Java ResultSet from JDBC code directly back to Flash. ResultSets are live, connected objects associated with pooled resources and I/O socket resources such as Statements and Connections. When a Statement or Connection is closed in JDBC code, all ResultSets associated with them are also automatically closed. Even if a user decides not to close a Statement or Connection, they could be closed and reclaimed by the application server at any time because they are pooled resources. If the ResultSet was closed for any reason, it will not be available to Flash Remoting for serialization. (#N-36858)
To return the data in a live ResultSet from a service method call, you must first create a disconnected ResultSet. The JDBC 2.0 API includes an interface, javax.sql.RowSet, that implements java.sql.ResultSet and is designed to be used in disconnected environments. Sun provides an implementation of RowSet called sun.jdbc.rowset.CachedRowSet that is suitable for this purpose and available from http://developer.java.sun.com/developer/earlyAccess/crs.
To convert a connected ResultSet to a disconnected ResultSet, create a RowSet from the connected ResultSet, as shown Example 7-4.
import java.sql.*; import javax.sql.*; import sun.jdbc.rowset.*; public class JavaClassService { public ResultSet getResultSet ( ) { Connection conn = null; try { DataSource ds = (DataSource) new InitialContext( ).lookup( "java:comp/env/jdbc/remotingbook"); conn = ds.getConnection( ); // Create and execute a SQL statement Statement stmt = conn.createStatement( ); ResultSet resultset = stmt.executeQuery("SELECT * FROM test"); // Create and populate a disconnected ResultSet (CachedRowSet) // from the connected ResultSet CachedRowSet rowset = new CachedRowSet( ); rowset.populate(resultset); // Close resources resultset.close( ); stmt.close( ); conn.close( ); // Return the disconnected ResultSet return rowset; // Exception handling omitted } catch (Exception e) { // handle exception } finally { // Close the connection if it exists and is not yet closed try { if (conn != null && !conn.isClosed( )) conn.close( ); } catch (SQLException e) {} } return null; }
Using this technique, the Remoting gateway converts the disconnected ResultSet to an ActionScript RecordSet. For details on using RecordSets in your Flash client, see Section 4.4.
To enable paging of the results of a service method call using the paging features of ActionScript's RecordSet object, the result of your service method must implement flashgateway.sql.PageableResultSet.
For paging of ResultSets, Macromedia provides flashgateway.sql.PagedResultSet, which implements flashgateway.sql.PageableResultSet and wraps a ResultSet. The constructor of PagedResultSet accepts the ResultSet to be wrapped and the page size. Using the getResultSet( ) method from Example 7-4 to obtain a disconnected ResultSet, we create and return a PageableResultSet as follows:
import java.sql.*; import javax.sql.*; import sun.jdbc.rowset.*; import flashgateway.sql.*; public class JavaClassService { public PageableResultSet getPagedResultSet( ) throws SQLException { // Create and return a PagedResultSet with 20 records per page return new PagedResultSet(getResultSet( ), 20); } public ResultSet getResultSet ( ) { // See Example 7-4 for implementation } }
When the Remoting gateway converts a Java object that is not one of the known types listed in Table A-4, it converts it to an ActionScript object using a strategy similar to standard Java serialization. For each Java object, it creates a new ActionScript object with properties that have the same name as the internal member variables, regardless of visibility, of the source Java object.
For example, given the following class:
public class User implements java.io.Serializable { private String _username; public User( ) {} public User(String username) { setUsername(username); } public String getUsername( ) { return _username; } public void setUsername(String username) { _username = username; } }
and the following service method:
public class UserService { public User getUser(String username) { return Directory.getUser(username); } }
a Flash client calling getUser( ) through Flash Remoting receives an ActionScript object with a single property: _username (not username, as a Java programmer might expect). The Remoting gateway uses a pass by value strategy for converting Java objects. It converts Java objects to ActionScript objects by creating a new ActionScript object for each Java object and converting the instance variables of the object to the type defined in the standard conversions. If the member instance is another regular Java object, it is converted according to the same rules.
There are a couple of issues with this approach. In Java development, a convention has emerged from the JavaBean specifications in which Java developers create public property accessors for private object state. They define an object's interface using methods called property accessors, named getPropertyName( ), setPropertyName( ), and isPropertyName( ). The java.beans Java APIs provide developers with an easy-to-use implementation for inspecting objects that describe themselves in this manner. Many APIs and frameworks including the Java Standard Tag Libraries (JSTL), and Jakarta Struts make extensive use of JavaBean property introspection.
Flash Remoting does not use an object's property accessors when converting a Java object to ActionScript. It uses the object's internal state, which ends up giving the Flash client a view of the object's internal state but not the interface defined by the developer of the Java object. Not only does this conversion technique expose information to Flash clients that is not available even to Java code running on the server, but it does not send the information to Flash that the developer of the Java object intended all clients of the object to see.
While Macromedia's pass by value approach for converting Java objects is one valid way to convert an object's state for use in Flash, it is not very useful for Java or Flash developers who end up with Java and ActionScript versions of the same objects that may look nothing alike. A more intuitive and useful approach is to convert Java objects to ActionScript using the Java object interfaces rather than their internal state. The following section discusses techniques for applying this approach.
Automatic conversion between Java objects and ActionScript objects using JavaBean introspection is provided by an open source project called ASTranslator (ActionScript Translator). ASTranslator is available and documented at http://carbonfive.sourceforge.net/astranslator and is sponsored by Carbon Five, Inc.
We have seen that service methods called through Flash Remoting can only take the Java objects listed in Table A-3 as their parameter types. This leaves the job of converting ASObjects to application-specific Java objects up to the Java developer. For simple objects, this is not a difficult task. However, for complex objects that are composed of other objects, this task quickly becomes tedious and the implementation becomes brittle.
ASTranslator gives Java developers a mechanism to convert ASObjects to native Java objects using JavaBean introspection. The following example uses ASTranslator to convert the asUser ASObject object to a User object:
import flashgateway.io.ASObject; import com.carbonfive.flash.ASTranslator; public class UserService { public void updateUser (ASObject asUser) { ASTranslator translator = new ASTranslator( ); User user = (User) translator.fromActionScript(asUser); Directory.updateUser(user); } }
ASTranslator requires that the type field of the ASObject parameter equal the class name of the Java object it should be converted to. With this information, ASTranslator converts a complex ASObject to a complex Java object arbitrarily deep. To set the type field, the ActionScript object must be registered in Flash with an identifier that equals the destination Java object class name.
The following example shows how to define an ActionScript User class and register it with a key equal to the class name of the User Java object on the server. This key becomes the value of the type field of the destination ASObject when the User object is sent by Flash to the Remoting gateway. This example creates a new ActionScript User object, sets its username property, and calls the updateUser( ) service method shown in the preceding code excerpt:
var service = gatewayConnection.getService( "com.oreilly.frdg.java.service.UserService", this); User = function ( ) {}; Object.registerClass("com.oreilly.frdg.java.user.User", User); var user = new User( ); user.username = "Flash User"; service.updateUser(user);
ASTranslator creates a new instance of the destination Java object type and sets its properties using JavaBean introspection according to the keys and their values in the source ASObject. The destination Java object must have a no-argument constructor and implement java.io.Serializable in order to be created by ASTranslator. ASTranslator preserves references as it does this conversion.
Table A-4 lists the datatype conversions that the Remoting gateway performs when going from Java to ActionScript. ASTranslator converts Java objects to ASObjects using JavaBean introspection so that the ASObject can be returned as the result of a service method call. ASTranslator respects the conversions explicitly handled by the Remoting gateway and preserves object references when converting Java objects to ActionScript objects. Any object that is not converted by ASTranslator is left as-is, to be converted by the Remoting gateway. The Remoting gateway converts Java ASObjects to ActionScript objects by creating a new ActionScript object for each ASObject and setting an ActionScript object property for each key in the ASObject. The Flash client receives ActionScript objects that have property names and values that match the original Java object properties on the server.
The following example converts a Java User object to an ASObject using ASTranslator and returns the ASObject. The Remoting gateway converts the ASObject return value to an ActionScript object that has the same properties as the properties of the original User Java object:
import flashgateway.io.ASObject; import com.carbonfive.flash.ASTranslator; public class UserService { public ASObject getUser (String username) { User user = Directory.getUser(name); return (ASObject) new ASTranslator( ).toActionScript(user); } }
Since they are being converted for sending over the network, Java objects must implement java.io.Serializable to be converted by ASTranslator.
ASTranslator also sets the type property of each ASObject it creates to the class name of the source Java object. Combined with the behavior of Object.registerClass( ), discussed in Chapter 4, and the ASTranslator convention of registering classes in ActionScript using the Java class name they should be converted to, ASTranslator facilitates a seamless mapping between ActionScript objects in Flash and Java objects in the server-side application.