R1-RPC/J User's Guide
R1-RPC/J: Documentation | Product Home

R1-RPC/J User's Guide

Version 1.2   July 2008
Table of Content
SectionSub-Sections
Introduction to R1-RPC/J The Issues and Needs of Flex-Java RPC
The R1-RPC/J Solution
The R1-RPC/J Toolkit and Development Process
Test-Driven Development of Client-Server Applications
What is in the R1-RPC/J Toolkit and How to Use It
Rules for Java Façade and VO Classes The Rules for Java Façade and VO Classes
Server-Client Type Conversion and Mapping
Accessing HTTP Request Objects in Façade Methods
RPC Programming with R1-RPC/J Server-Side Build and Deploy
Creating the Client-Side Façade Counterpart Classes
Creating and Using Façade Interfaces
Client-Side Programming
The Callback for RPC and Exception Handling
Using R1-RPC/J with Java Frameworks Using R1-RPC/J with the Spring Framework
Using R1-RPC/J with Other Java Frameworks
Defining VO Classes Bindability of VOs and VO Properties
Using VOs as a Part of the Client-Side Data Model
VO Class Inheritance and Inner VO Classes
Understanding Login with R1-RPC/J The LoginHandler and User VO
Authenticated Remote Methods
The Client Login Programming
CHAP User Registration and Login The Challenge Handshake Authentication Protocol (CHAP)
Using CHAP Login with R1-RPC/J
Aspect-Oriented Programming (AOP) for RPC  
Cooperative Polling and RPC-Piggyback Messaging Service  
Using R1-RPC/J for Flash RPC  
RPC Transport Considerations  
Automated RPC Unit Testing Test Case Specification Tags
The Generated Test Suite Class
Control the Order of Test Cases
Using Login and Test Authenticated Methods
Creating Password Digest for Unit Testing with CHAP Login
Creating Custom Unit Test Cases Programmatically
Using R1-RPC/J Tools Using R1-RPC/J Command-Line Code Generation Tools
Using R1-RPC/J Code Generation Ant Tasks

 


Introduction to R1-RPC/J

The RiaOne RPC for Java, or R1-RPC/J, is an RPC bridge between Java™ server side and RIA client side, especially Flex™ and Flash™ (henceforward, Flex and Flash) programs. It is designed to be easy-to-use, robust, and versatile. As a key system componet in RIAs, R1-RPC/J is designed to elicit good system design, and foster good software engineering practices, such as test-oriented development, clear delineation of software responsibilities, and good maintainability. In addition to RPC, R1-RPC/J also supports user registration and authentication and handles session timeout. It completely frees application developers from such logistical system tasks, so that they can focus on developing business logic and killer user interfaces. For more general discussions, refer to related literature such as the R1-RPC/J White Paper and R1-RPC/J Frequently Asked Questions.

Before getting started with R1-RPC/J, let us first understand the problems it tries to address, and the ideas behind this solution.

The Issues and Needs of Flex-Java RPC

We know that Flex and Flash provide a number of remoting mechanisms. Some of them talk to Adobe's proprietary server technologies such as media streaming; most others are low-level network accessing including sockets, HTTP and SOAP web services. The data sent and received on both ends are typically of very primitive data types; as soon as big systems are getting developed with Flex, the need of remote data abstraction becomes obvious and imperative.

ActionScript 3 (AS3) is a strongly-typed object-oriented language. Its design and its implementation are undoubtedly influenced by Java. Programming to objects would be the most natural approach for Flex-Java applications. However, the reality is quite far from this seemingly innocent and reasonable wish, due to two major obstables — logitic and conceptual.

The first issue is the logitics of mapping data types and objects between the two languages. For instance, Java has various generic java.util. data structures; AS3 only has Object and Array and a few collection classes. Also, Java has more primitive data types than AS3.

The second issue is the confusion of object model serialization across different program spaces. This is a general problem for remoting mechanisms. Because objects inherantly contains and/or references other objects, any object in an object model is effectively a node in an object web. How to serialize a web of objects across different agents (client and server)? We must have a definitive answer to this before even designing an RPC solution.

The R1-RPC/J Solution

The R1-RPC/J solution is built upon an assumption that the server side exposes its services as methods in a simple Java façade class. The data that are transferred between the client and server are limited to simple data types, user-defined value objects (VOs), and container data structures such as Java arrays, maps and lists. All objects and containers must use containment of other objects, not object references.

This restrictions may seem limiting; in reality, nothing is really sacrificed. The reason is that, client and server programs are running in their own spaces; they both have very disparate object models for very different purposes. The client-side object model, for instance, is primarily to satisfy the needs of the UI and the remote service requests, where the server-side object model is for business logic. Passing object webs to the other end is neither necessary nor desired. What should be passed back and forth is the data necessary to establish or refresh the respective object models, and VOs are exactly appropriate for this purpose.

Server-side VO classes may seem like an overhead in the overall system; but chances are, many of these classes (or, more accurately, their generated client-side counterpart) can be used directly in the client program, so they effectively become part of the client object model. In other words, many of the VO classes are client-side model classes written on the server-side. For this reason, R1-RPC/J supports embedded AS3 code in javadoc comment within VO Java source code.

The R1-RPC/J Toolkit and Development Process

Figure 1 below shows the overall development landscape of a client-server application using R1-RPC/J as its RPC mechanism.

R1-RPC/J Application Architecture
Figure 1. R1-RPC/J Application Architecture

The three green parts of the diagram are where you need to write code for. The client UI and server-side business logic are the rightful parts of the development effort. R1-RPC/J handles and hides the remote communication completely from the programmers with the only exception of the help that the developer provide Java façade and the related VO classes. The R1-RPC/J code generation tools generate AS3 classes that correspond to Java façade and VO classes. The generated classes are so close to their Java counterpart, you are practically calling Java from AS3 (plus a callback function required by AS3 for making asynchronous calls).

In addition to RPC, R1-RPC/J also supports user registration and login and user server session timeout control. You will need to provide a login handler for creating and manipulating user information, such as saving to and retrieving from a user database. R1-RPC/J sports a Challenge Handshake Authentication Protocol (CHAP), making the user authentication process secure enough for many medium-level secure web applications.

Another cool feature of R1-RPC/J is its AOP support for RPC. You can specify an AOP interceptor for a whole façade class or some of the methods; the interceptor will receive call-start, call-end, call-return and exception events. What to do with them is totally up to the developer.

Test-Driven Development of Client-Server Applications

R1-RPC/J can also generate unit test cases for each façade methods that have test case input and output present in their javadoc comments. Part of the Ant build script can automatically build a GUI tester for these test cases. This can be done any time a façade class is defined, even before the client program is started.

This automated RPC testing support is especially great for a better team development environment. At the earliest stage of development, the server team can provide a fake implementation and the client team can start developing UI right away through the interface. In the mid of development, RPC unit testing can quickly point out where really lies a bug. Throughout the life cycle of the project and deep into maintenance mode, they will safe guard any changes made to the server side. This is invaluable.

What is in the R1-RPC/J Toolkit and How to Use It

The R1-RPC/J software includes everything you need to develop with the R1-RPC/J solution. It includes the client-side and server-side libraries, code generation tools including Ant taskdef's, sample applications with Ant build scripts and the ready-to-run J2EE war files, and complete documentation.

The samples' build scripts are almost identical except for the Flash sample. They are a good place to start; but you need to modify the entries in the accompanying env.properties file to reflect your build environment, such as SDK locations, application name, etc. The result of a build is a J2EE war file, which includes the intended application and an RPC unit testing application. The latter is valid only if some of the façade methods have test case input and output specified; otherwise, it would be useless because there would not contain any test cases.

The downloaded package is fully functional software. For production deployment, you must purchase a valid license file, r1rpc.lic. This file is to be put in the root of the Java CLASSPATH. For those samples, simply keeping it with the build.xml file suffices. Without this license file at runtime, your server is running in the evaluation mode, which is perfectly good for developing purposes.

While Ant building is convenient, a manual development exercise is definitely helpful to understand the process better. The R1-RPC/J Tutorial provides a few step-by-step hands-on excercises to build and run some simple Flex- and Flash-Java applications. It is highly recommended that you do these first prior to reading on.


Rules for Java Façade and VO Classes

In R1-RPC/J, there are some simple rules governing the Java façade and VO classes. These rules help to clearly define the problem space, and constitute the foundation of this very solution.

The Rules for Java Façade and VO Classes

  1. The Java façade classes can not have overloaded methods.
    Reason: because ActionScript 3 does not support class method overloading.
  2. The Java façade and VO classes must implement signature interfaces r1.rpc.RemoteFacade and r1.rpc.RemoteVO, respectively.
    Reason: for the code generation tool to pick them up and also documentation purposes.
  3. Data structures over the wire can not have references, only containment.
    Reason: because object referencing is a manifestation of object relationship, which is a part of an object/data model. Since server- and client-side object models are separate and not related, object referencing is meanless and hence prohibited to avoid confusion.

The Java façade methods' parameter and return types are constrained to these:

The Java VO classes have similar constraints:

Server-Client Type Conversion and Mapping

Data types are mapped between Java and AS3 at code-generation time and runtime. The following table enumerates the Java types and their generated AS3 counterparts:

Java TypeAS3 Type
boolean, java.lang.Boolean Boolean
byte, java.lang.Byte
short, java.lang.Short
int, java.lang.Integer
long, java.lang.Long
int
float, java.lang.Float
double, java.lang.Double
Number
java.lang.String
char, java.lang.Character
char[], java.lang.Character[]
String
java.util.Date
java.util.Calendar
java.util.GregorianCalendar
java.sql.Date
java.sql.Time
java.sql.Timestamp
Date
*[] (except for char[] and Character[])
java.util.List
java.util.AbstractList
java.util.ArrayList
java.util.LinkedList
java.util.Vector
java.util.Set
java.util.AbstractSet
java.util.LinkedHashSet
java.util.TreeSet
java.util.HashSet
Array
java.util.Map
java.util.AbstractMap
java.util.HashMap
java.util.TreeMap
java.util.Hashtable
Object
Custom VO Class Generated Custom VO Class

Table 1. Server-Client Type Mapping

AS3 does not support set data structure, so it is recommended not to use java.util.Set for façade method. In case they do appear, they are serialized into Array on the client side.

Accessing HTTP Request Objects in Façade Methods

The façade class methods are regular Java methods. Usually the code need to access the user object and possibly other HTTP request parameters. The R1-RPC/J runtime engine stores many such objects in a thread-local storage. They are accessible via the r1.rpc.WebTierUtils class, whose methods are all static. The following is a list of its major methods:

Useful methods of r1.rpc.WebTierUtils:

               void checkAuthentication();
        HttpServlet getServlet();
      ServletConfig getServletConfig();
     ServletContext getServletContext();
 HttpServletRequest getServletRequest();
HttpServletResponse getServletResponse();
        HttpSession getSession();
             Object getSessionAttribute(String key);
             Object getUser();
             Object getUser(HttpSession session);
            boolean isLoggedIn();
               void removeSessionAttribute(String key);
             Object setSessionAttribute(String key, Object val);
               void setUser(Object user);

RPC Programming with R1-RPC/J

Let us start with the simplest possible RPC use case. We like to provide a remote call in Java that says "Hello" to the incoming name. The following is the source code of the façade class:

package sample;

public class FriendlyFacade implements r1.rpc.RemoteFacade
{
    /**
     * @ASTestInput [ 'James' ]
     * @ASTestResult x == 'Hello, James!'
     */
    public String sayHi(String name) {
        return "Hello, " + name + '!';
    }
}

We notice that this is a plain Java class that implements the r1.rpc.RemoteFacade interface, which is empty. The method for the remote service, sayHi(), is just a regular method. The method's special javadoc tags in comment are for unit testing, and will be discussed later. Usually façade class methods are thin delegates to service implementation code; in this simple example, we just implement it here.

Server-Side Build and Deploy

Compile this Java class, and put it into the appropriate J2EE web application deployment structure as follows:

{webapp_name}/WEB-INF/classes/sample/FriendlyFacade.class
{webapp_name}/WEB-INF/classes/r1rpc.lic
{webapp_name}/WEB-INF/lib/r1rpc-1.3.jar
{webapp_name}/WEB-INF/web.xml

The r1rpc.lic may be absent for development purposes. The web.xml is like so:

<web-app ... >

  <servlet>
    <servlet-name>R1RPCServlet</servlet-name>
    <servlet-class>r1.rpc.RPCServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>R1RPCServlet</servlet-name>
    <url-pattern>/rpc</url-pattern>
  </servlet-mapping>

</web-app>

This designates http://host:port/webapp/rpc as the URL for RPCs from clients.

Creating the Client-Side Façade Counterpart Classes

As soon as a Java façade class is created or updated, you must run the code generation tool to create the client-side counterpart. The tool is a Java program, r1.tools.java2client.ASCodeGen; it requires JDK_HOME/lib/tools.jar and the Apache Velocity template engine. Assuming r1tools.jar, JDK_HOME/lib/tools.jar, and the Velocity template engine library jar files are in CLASSPATH, the tool's command line is:

java r1.tools.java2client.ASCodeGen -src server_src -dest client_src sample

This will generate a sample/FriendlyFacade.as file in the destination directory; its content is like this:

package sample
{
import r1.core.r1_internal;
import r1.rpc.RPC;
import r1.rpc.RPCFacade;

public class FriendlyFacade extends RPCFacade
{
    /**
     * @param rpc the RPC connection to use. If it is null,
     *        the default RPC connection singleton is used, which is
     *        initialized by a call to RPC.initSingleRPC().
     * @param encoding the data encoding used in RPC calls for all the
     *        methods in this class. Default is 0 (RPC.RPC_DEFAULT).
     */
    public function FriendlyFacade(rpc:RPC=null, encoding:int=0) {
        super(rpc, encoding);
    }

    /////////////////////////////////////////////////////////////////////////////
    //
    // In the facade methods, the first parameter, receiver, can be one of
    // the following:
    //
    // 1. A callback function with this signature:
    //
    //    function callback(retVal:ReturnType=null, err:ErrorResult=null):void;
    //
    // 2. An array of [ host, field ], where host[field] is to receive the value.
    //    Failure will be handled by the system.
    //
    // 3. An array of [ host, field, failureCallback ], where host[field] is to
    //    receive the value, and in case of failure, the failureCallback will be
    //    invoked; this function has this signature:
    //
    //    function failureCallback(err:ErrorResult):void;
    //
    // receiver can be null, meaning to use default error handling and
    // ignore the status of the call or its return value, if any.
    //
    /////////////////////////////////////////////////////////////////////////////

    /**
     * If receiver is a callback, its signature is:
     *     function cb(returnValue:String, errMsg:String):void { }
     */
    public function sayHi(receiver:*, name:String):void {
        r1_internal::call("sayHi", [name], receiver);
    }

} // end of class FriendlyFacade.
} // end of package.

The client-side method sayHi() is identical to the server-side counterpart with the exception of the added first parameter, receiver. This is required by the nature of AS3 remote calls, and its use is well explained in the comment block.

The constructor is discussed below.

Creating and Using Façade Interfaces

The code generation tool, r1.tools.java2client.ASCodeGen, has a -useFacadeInterface option to generate a façade interface along with the class, and the class implments that interface.

This may be desired in certain system designs; one possibility is that the system uses a factory to provide different RPC mechanisms under different usage situations. Another possiblity is that the client team likes to or needs to start coding without a server, and they wish to fake the RPC on the client side. Using interface in this situation will completely shield client code from server-side dependancy.

Run the above command line with the added -useFacadeInterface option, you will get these files generated:

package sample
{
public interface IFriendlyFacade
{
    function sayHi(receiver:*, name:String):void;
}
}
package sample
{
import r1.core.r1_internal;
import r1.rpc.RPC;
import r1.rpc.RPCFacade;

public class FriendlyFacade extends RPCFacade implements IFriendlyFacade
{
    public function FriendlyFacade(rpc:RPC=null, encoding:int=0) {
        super(rpc, encoding);
    }

    public function sayHi(receiver:*, name:String):void {
        r1_internal::call("sayHi", [name], receiver);
    }
}
}

Client-Side Programming

From client, remote calls are sent to the server through an r1.rpc.RPC object. This RPC object is initialized with the remote call URL, and is serving as a channel; each façade is associated with one RPC as specified in the constructor. A single RPC can be used for any number of façades.

In most situations, a single channel is good enough for the whole application. Class r1.rpc.RPC keeps a static defaultRPC instance, which is initialized by the RPC.initSingleRPC() static method. It takes the same parameters as RPC's constructor:

public function RPC(url:String, encoding:int=RPC_DEFAULT, sessionNeverTimeout:Boolean=true)

The first parameter, url, is the URL for RPC servlet on the server-side; it can be relative. The encoding parameter specifies the default transport encoding; it can be overridden by a façade. At this writing, using RPC_DEFAULT is fine; more formats will be developed in the near future. The last parameter, sessionNeverTimeout, is boolean that when set to true, the server session never times out, as this RPC will ping the server before it is idle for too long.

In the façade class's constructor, you can pass null as its first parameter to indicate that this façade will use RPC.defaultRPC for its remote calls; just make sure that RPC.defaultRPC is initialized before any calls can be made.

Let us take a look at a real client program that uses our FriendlyFacade.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application layout="absolute"
    xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="*"
    creationComplete="RPC.initSingleRPC('rpc')">

<mx:Script><![CDATA[
import r1.rpc.RPC;
import r1.rpc.RPCResult;
import sample.FriendlyFacade;

private var facade:FriendlyFacade = new FriendlyFacade();

private function sayHiCB(result:String, rpcResult:RPCResult):void {
    if (!rpcResult.success) {
        output.text = "Error: " + rpcResult.errorDetail.message
    } else {
        var txt:String = output.text;
        if (txt == null || txt == '')
            output.text = result
        else
            output.text = txt + '\n' + result
    }
}
]]></mx:Script>

    <mx:Form label="Direct Call" width="100%">
        <mx:FormItem label="Name:" direction="horizontal">
            <mx:TextInput id="myName"/>
            <mx:Button label="Say Hi!"
                click="facade.sayHi(sayHiCB, myName.text)"/>
        </mx:FormItem>
        <mx:FormItem label="Output:" width="100%">
            <mx:TextArea id="output" width="100%" height="200"/>
        </mx:FormItem>
    </mx:Form>

</mx:Application>

This client program is a simple UI; it is deployed at the root of the J2EE application, so the relative remote URL is "rpc". The rest of the code is self-evident.

Simple RPC UI
Figure 2. Simple RPC UI

The Callback for RPC and Exception Handling

The callback function for each remote call takes two parameters. The first is the return value (null if it is a void function) if the call returns successfully. The second parameter is an r1.rpc.RPCResult object. If the call is successful, the same RPC.SUCCESS object is returned; otherwise, an instance of r1.rpc.ErrorResult, which extends RPCResult, is returned (and the return value parameter is always null.) These two classes are essentially defined as:

public class RPCResult extends Event
{
    public static const RPC_SUCCESS:String = "success";

    public function RPCResult(type:String) { super(type); }

    public function get success():Boolean { return this.type == RPC_SUCCESS }
    public function get failed():Boolean { return !success }
    public function get errorDetail():ErrorResult { return null }
}

public class ErrorResult extends RPCResult
{
    private static const SYSERR_PREFIX:String = "SYSERR::";

    public static const RPC_URL_NOT_SET      :String = SYSERR_PREFIX + "RPC_URL_NOT_SET";
    public static const RPC_IO_ERROR         :String = SYSERR_PREFIX + "RPC_IO_ERROR";
    public static const RPC_ERROR            :String = SYSERR_PREFIX + "RPC_ERROR";

    public static const SERVER_CONFIG_ERROR  :String = "SERVER_CONFIG_ERROR";
    public static const SERVER_AUTH_FAILURE  :String = "SERVER_AUTH_FAILURE";
    public static const SERVER_ERR_PARSE     :String = "SERVER_ERR_PARSE";
    public static const SERVER_ERR_NO_METHOD :String = "SERVER_ERR_NO_METHOD";
    public static const SERVER_ERR_UNMARSHALL:String = "SERVER_ERR_UNMARSHALL";
    public static const SERVER_ERR_MARSHALL  :String = "SERVER_ERR_MARSHALL";

    private var _message:String;

    public function ErrorResult(type:String, msg:String) { super(type); _message = msg; }

    public function systemError():Boolean { return type.indexOf(SYSERR_PREFIX) == 0 }

    public function get message():String { return _message }

    override public function get errorDetail():ErrorResult { return this }
}

Notice that r1.rpc.RPCResult is a flash.events.Event. In fact, r1.rpc.RPC extends flash.events.EventDispatcher; when a remote call fails, an event is dispatched with the ErrorResult object. You can listen for and handle specific error messages.

A remote call may fail for many reasons. The remote call may not go through for whatever physical or network reasons; they are represented by RPC_URL_NOT_SET, RPC_IO_ERROR, and RPC_ERROR. The server may not function for a given request; these are represented by the SERVER_xxxx names. The SERVER_AUTH_FAILURE error is particularly interesting; it happens when a user update fails or whenever a javax.security.auth.login.LoginException occurs.

Beyond these system-level exceptions, the remote call itself may throw Java exceptions. Server-side Java exceptions are returned on the client side as an ErrorResult; the name is prefixed with "JAVA::". For instance, if an exception of sample.MyException is thrown, the name of the ErrorResult is "JAVA::sample.MyException".


Using R1-RPC/J with Java Frameworks

While the R1-RPC/J model of using façade pattern exerts little disturbance to the server side programming, it can be more tightly integrated into Java frameworks, so that the programming and configuration details are more conforming to the overall server side coding standard. R1-RPC/J includes built-in support for using the Spring Framework, and includes the source code for both RPCServlet and RPCSpringServlet as reference implementations should you need to integrate with other frameworks.

Using R1-RPC/J with the Spring Framework

The façade pattern used exclusively by R1-RPC/J makes R1-RPC/J applications perfect candidate for the Spring Frameworks. R1-RPC/J includes a RPCSpringServlet, which extends org.springframework.web.servlet.FrameworkServlet. This Spring servlet class is contained in spring-webmvc.jar; at the minimum, you will need that and spring-webmvc.jar deployed in the webapp.

The key differences between Spring and non-Spring R1-RPC/J applications are:

  1. Configure the webapp to use RPCSpringServlet, and
  2. Must specify all façades, along with optional login and message handlers, as Spring beans. (This is hardly a requirement, because it is the very reason you want to use R1-RPC/J with the Spring framework in the first place.) The façade beans must use the class name, either fully qualified or the bare class name, for R1-RPC/J runtime engine to look up.

The following is a sample deployment of R1-RPC/J application with Spring.

{webapp_name}/WEB-INF/lib/r1rpc-1.3.jar
{webapp_name}/WEB-INF/lib/spring.jar
{webapp_name}/WEB-INF/lib/spring-webmvc.jar
{webapp_name}/WEB-INF/web.xml
{webapp_name}/WEB-INF/app-config.xml

The web.xml is:

<web-app ... >

  <servlet>
    <servlet-name>R1RPCSpringServlet</servlet-name>
    <servlet-class>r1.rpc.RPCSpringServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>R1RPCSpringServlet</servlet-name>
    <url-pattern>/rpc</url-pattern>
  </servlet-mapping>

</web-app>

The app-config.xml is:

<beans>
  <bean id="FriendlyService" class="sample.MyServices" />

  <bean id="loginHandler" class="sample.MyLoginHandler" >
    <property name="service" ref="FriendlyService" />
  </bean>

  <bean id="AuthenticatedFriendlyFacade"
           class="sample.AuthenticatedFriendlyFacade">
    <property name="service" ref="FriendlyService" />
  </bean>
</beans>

There are setter and getter methods in both MyLoginHandler and AuthenticatedFriendlyFacade classes for the service property. Functionally, this example is identical to another example, simple_login.

Using R1-RPC/J with Other Java Frameworks

In the R1-RPC/J distribution, under the resources/, you will find some of the source code files. To create a custom servlet class to integrate with a new Java framework of your choice, locate and refer to RPCServlet.java and RPCSpringServlet.java. The R1-RPC/J functionality is implemented in class RPCActions. Your servlet class needs to do three things:

  1. It must implement the RPCConfiguration interface to provide methods to locate loginHandler, messageHandler and façade objects at runtime.
  2. Create an instance of RPCActiosn at startup.
  3. For HTTP requests, delegate service handling to the RPCActions.service() method.

Defining VO Classes

VO classes must all implement the r1.rpc.RemoteVO interface, and must comply with the rules.

VOs are meant to carry values, so typically they only have properties and getter/setter methods; these are replicated on the client-side as class properties, while regular methods are simply ignored. In case a property also has a getter and/or setter defined, a single property is generated in the client-side VO class. Note that for boolean getters, it must be defined like any other getters; that is, methods such as isXXX() are not considered getters, unlike the JavaBean convention.

The R1-RPC/J code generator supports a number of javadoc tags within Java VO classes at both class and property levels. At class level, there are @extends, @implements, @bindable, and @postConstruction. At property level, there are @bindable, and @private. In any javadoc comments, AS3 code can be embedded in @as3 or @ActionScript tags. The code will be copied in the generated AS3 class verbatim. Keep in mind that javadoc comments must precede either a class, field, or method declaration, not anywhere in the class body.

Bindability of VOs and VO Properties

The @bindable tag can precede either class or property and getter/setter methods; it is converted to [Bindable] in the generated code for the class or the property. The @private tag signifies that the property would not be present in the client-side class.

Using VOs as a Part of the Client-Side Data Model

The tags of @extends, @implements, postConstruction, and @as3 are designed to produce extra AS3 code in the client-side VO classes, in addition to the Java VO properties. With these, the generated AS3 VO classes can be effectively as UI-side object model classes.

The @extends and @implements tags allows the generated AS3 class to extend and/or implement other regular AS3 classes and interfaces; in the @as3 tags, you can write any extra code to override or implement necessary methods.

The @postConstruction tag takes AS3 code; this code will be copied to the end of the constructor in the generated AS3 class. Since VOs cannot have object references during serialization, this post construction code can be used to invoke client-side functions to update the intended part of object model.

Let us see an example. The following shows a regular AS3 class and an interface in package test.exotic:

public class MyBase
{
    public function getName():String { return null }
}

public interface MyItf
{
    function get description():String;
}

The following is a server-side VO class:

package test;

/**
 * @extends test.exotic.MyBase
 * @implements test.exotic.MyItf
 * @bindable
 * @postConstruction
 *   trace("MyChildExoticVO: name=" + this.name)
 *
 * @ActionScript
 *   public function get description():String { return "MyChildExoticVO" }
 *   public override function getName():String { return name }
 */
public class MyChildExoticVO implements r1.rpc.RemoteVO
{
        public String name;
}

The generated client-side VO class is:

package test
{
import r1.rpc.RPCUtils;
import test.exotic.MyItf;
import test.exotic.MyBase;

[Bindable]
public class MyChildExoticVO extends test.exotic.MyBase
        implements test.exotic.MyItf
{
    public var name:String;

    public function MyChildExoticVO(init:Object=null) {
        super();
        if (init != null)
            RPCUtils.populate(this, init);

        trace("MyChildExoticVO: name=" + this.name)
    }

    public function get description():String { return "MyChildExoticVO" }
    public override function getName():String { return name }
}
}

VO Class Inheritance and Inner VO Classes

R1-RPC/J supports VO class inheritance, including extending abstract VO classes. Abstract VO classes are also generated on the client-side for derived VO classes. At runtime, abstract VO classes should not be instantiated and never be passed in remote calls to the server.

Static inner VO classes inside a VO class are supported. See the example below.

package test.livingThing;

public abstract class Animal implements r1.rpc.RemoteVO
{
    private String _name;
    public String getName() { return _name; }
    public void setName(String n) { _name = n; }

    public abstract String getSkinType();

    public boolean getHasFur() { return _skinIs("fur"); }
    public boolean getHasFeather() { return _skinIs("feather"); }

    private boolean _skinIs(String s) { return s.equals(getSkinType()); }

    // Inner class
    public static class Panda extends Animal
    {
        public String getSkinType() { return "fur"; }

        // Inner class
        public static class RentedPanda extends Panda
        {
            public boolean getIsRented() { return true; }
        }
    }

    // Inner class
    public static class Parrot extends Animal
    {
        public String getSkinType() { return "feather"; }
    }

} // end of class Animal. 
}

The inner classes will be generated on the client side with a dollar sign ($) between the names of the inner and outer class. Otherwise, they are no difference then other AS3 classes. For instance, the generated Animal$Panda$RentedPanda looks like this:

package test.livingThing
{
import r1.rpc.RPCUtils;

public class Animal$Panda$RentedPanda extends Animal$Panda
{
    public var isRented:Boolean;

    public function Animal$Panda$RentedPanda(init:Object=null) {
        super();
        if (init != null)
          RPCUtils.populate(this, init);
    }
}
}

Understanding Login with R1-RPC/J

User authentication is virtually required in every enterprise application. R1-RPC/J runtime engine includes a complete user authentication system with Challenge Handshake Authentication Protocol (CHAP). With CHAP, a dynamic digest of the password texts are passed across the wire, never the password itself. This should suffice for many applications, especially when coupled with secure connection schems such as encoded transport.

For the convenience of development, a plain-text password simple login system is also supported. Its programming is similar to that of CHAP, but is easier to understand. This is where we start.

The LoginHandler and User VO

To enable user authentication in an R1-RPC/J-based system, first you need to provide a LoginHandler and specify it in the web.xml like so:

<web-app ... >

  <servlet>
    <servlet-name>R1RPCServlet</servlet-name>
    <servlet-class>r1.rpc.RPCServlet</servlet-class>
    <init-param>
      <param-name>loginHandlerClass</param-name>
      <param-value>sample.MyLoginHandler</param-value>
    </init-param>
  </servlet>

  <servlet-mapping>
    <servlet-name>R1RPCServlet</servlet-name>
    <url-pattern>/rpc</url-pattern>
  </servlet-mapping>

</web-app>

The login handler class must extend r1.rpc.LoginHandler, which has these methods for you to override and implement:

public String getPasswordDigest(String id) throws PasswordNotRetrievableException
    { throw PasswordNotRetrievableException.THE_ONE; }
public abstract void savePasswordDigest(String id, String digest);
public abstract String createUser(String id, String digest, Object data);
public abstract Object loadUser(String id, String password_digest);
protected boolean doEncrypt() { return true; }

The underlying login implementation handles two scenarios: (a) user password can be retrieved and compared, and (b) user password can not be retrieved but is used to load the user object. Either way, at the end of a successful login, a user object is obtained and stored in the session. In the first scenario, where the user password (or digest) is stored in a database, both getPasswordDigest(id) and loadUser(id, password) are used, but the password parameter in the latter is typically ignored. The second scenario is that password is not stored in a database; for instance, the user is authenticated using the operating system or network domain account. In this case, getPasswordDigest(id) is left untouched (which throws the PasswordNotRetrievableException), and loadUser(id, password) is solely responsible for obtaining the user object using both id and password values.

Methods createUser(), savePasswordDigest() and getPasswordDigest() are for user registration and login using CHAP. This is typically necessary because with CHAP, only password digests are transmitted across the wire but never the passwords themselves. It is possible (and often necessary) to generate user password digests off-line, but on-line user registration is probably desired in web applications. More on CHAP later.

The return value of doEncrypt() indicates whether CHAP login is used or not. It defaults to use CHAP.

In this section, we do not use CHAP and simply hard-code the user information in memory. The login handler is very simple:

package sample;

public class MyLoginHandler extends r1.rpc.LoginHandler
{
    protected boolean doEncrypt() {
        return false;
    }

    public String getPasswordDigest(String id) {
        return MyServices.getPasswordDigest(id);
    }

    public void savePasswordDigest(String id, String digest) {
        MyServices.savePasswordDigest(id, digest);
    }

    public String createUser(String id, String digest, Object data) {
        return null; // not used.
    }

    public Object loadUser(String id, String digest) {
        return MyServices.getUser(id);
    }
}

The logic of user authentication is delegated to a service class, MyServices. It returns a user object of class UserVO, which is a VO class itself:

package sample;

public class UserVO implements r1.rpc.RemoteVO
{
    public String id;

    /**
     * @bindable
     */
    public String name;

    public String desc;

    /**
     * @private
     */
    public String password;

    public UserVO(String id, String password, String name, String desc) {
        this.id = id;
        this.password = password;
        this.name = name;
        this.desc = desc;
    }
}

Authenticated Remote Methods

R1-RPC/J runtime engine uses annotation to enforce remote methods authenticated:

package sample;

import r1.rpc.LoginHandler;
import r1.rpc.RemoteFacade;
import r1.rpc.aop.RPCInterceptor;

@RPCInterceptor(checkAuthentication="yes")
public class AuthenticatedFriendlyFacade implements RemoteFacade
{
    public String sayHi() {
        return "Hello, " + ((UserVO)LoginHandler.getUser()).name + '!';
    }
}

In this example, all methods in façade AuthenticatedFriendlyFacade are authenticated. What happens on the server is that, after the user has logged in, the user object is stored in the session; this user object can be retrieved via the static LoginHandler.getUser() method. If a remote method needs be authenticated, R1-RPC/J runtime checks the availability of the user object for the session; if the user object is not found, hence the user is not logged in, a javax.security.auth.login.LoginException is thrown.

The Client Login Programming

On the client side, the r1.rpc.RPC class has a login() and logout(). The following code is for a simply UI with two screens:

<?xml version="1.0" encoding="utf-8"?>
<Application xmlns="http://www.adobe.com/2006/mxml"
    creationComplete="init()" layout="absolute">

<Script><![CDATA[
import mx.controls.Alert;
import r1.rpc.RPC;
import r1.rpc.RPCResult;
import r1.rpc.ErrorResult;
import sample.UserVO;
import sample.AuthenticatedFriendlyFacade;

private var facade:AuthenticatedFriendlyFacade = new AuthenticatedFriendlyFacade;

[Bindable] public var user:UserVO;

private function init():void {
    RPC.initSingleRPC('rpc');
    RPC.defaultRPC.passwordEncrypted = false; // default is true.
}

private function doSignIn():void {
    RPC.defaultRPC.login([this, 'user', errorHandler], username.text, password.text);
    password.text = '';
}

private function doSignOut():void {
    user = null;
    RPC.defaultRPC.logout();
}

private function cb(result:String, rpcResult:RPCResult):void {
    if (!rpcResult.success) {
        errorHandler(rpcResult.errorDetail);
    } else {
        Alert.show('Returned: ' + result);
    }
}

private function errorHandler(err:ErrorResult):void {
    Alert.show('[' + err.type + '] ' + err.message);
}
]]></Script>

<ViewStack width="100%" height="100%" selectedIndex="{user==null ? 0 : 1}">

    <Form label="Login" width="100%">
        <FormItem label="Name:">
            <TextInput id="username" text="test"/>
        </FormItem>
        <FormItem label="Password:">
            <TextInput id="password" text="test" displayAsPassword="true"/>
        </FormItem>
        <FormItem>
            <Button label="Sign In" click="doSignIn()"/>
        </FormItem>
        <FormItem>
            <Button label="Try to Say Hello" click="facade.sayHi(cb)"/>
        </FormItem>
    </Form>

    <VBox width="100%" height="100%">
        <Label id="caption" text="Welcome, {user.name}!" fontSize="20"/>
        <Spacer height="10"/>
        <Button label="Say Hello to Myself" click="facade.sayHi(cb)"/>
        <Button label="Sign Out" click="doSignOut()"/>
    </VBox>

</ViewStack>

</Application>

The first screen is for login:

The Simple Login Sample Screen
Figure 3. The Simple Login Sample Screen

If you click on the "Try to Say Hello" button, an exception will be received because the user has not logged in yet. Clicking on the "Login" button, it will go to the second screen; then, the "Say Hello to Myself" button will return the expected message:

The Second Screen of Simple Login
Figure 4. The Second Screen of Simple Login


CHAP User Registration and Login

The Challenge Handshake Authentication Protocol (CHAP)

Having passwords transmitted as plain-text is not a comfortable thought. CHAP is a scheme that makes login process significantly safer because: a) Password never travels across the wire, and b) Clear password text is not stored on the server. It works like this.

  1. When the users register and create a new account, they type in a password. The client side sends the digest of the password, along with other information, to the server, and the server stores this password digest.
  2. Before the user starts to login, the server first sends a challenge, a random seed, to the client.
  3. The user types in the user ID and password and tries to login. The client side first gets the digest of the user password, and combines this hashed value with the challenge, and calculates a new digest, which is sent to the server.
  4. The server side uses the user ID to retrieve the stored user's password digest, then use the same challenge value to do another hashing just like the client side did, and compare the values. If they match, the user is successfully authenticated.

In short, CHAP only stores the digest of user passwords; during the login process, CHAP uses a dynamic challenge to further hash the password digest. It would take a lot of monitoring and computation to crack this scheme. CHAP login is quite secure for most applications.

Using CHAP Login with R1-RPC/J

R1-RPC/J runtime engines intrinsically handle CHAP login. All you have to do is, like in simple login situation, provide your own LoginHandler; most likely, you need to implement the createUser() method. Because R1-RPC/J is defaulted to use CHAP, you actually have to do less initialization as compared to the simple login usage.

package sample;

public class MyLoginHandler extends r1.rpc.LoginHandler
{
    public String getPasswordDigest(String id) {
        return MyServices.getPasswordDigest(id);
    }

    public String createUser(String id, String digest, Object data) throws Exception {
        return MyServices.createUser(id, digest, (UserVO)data);
    }

    public void savePasswordDigest(String id, String digest) {
        MyServices.savePasswordDigest(id, digest);
    }

    public Object loadUser(String id, String digest) {
        return MyServices.getUser(id);
    }
}

The createUser() method returns the ID for the new user. The method takes an id as parameter; if it is not null, the login handler should check for its availability before creating a user for the ID. Otherwise, the login handler should create a new ID. The following sample UI registers a new user, then shows the login page with the new ID for user to login.

The CHAP Login Sample Screens
The CHAP Login Sample Screens
The CHAP Login Sample Screens
Figure 5. The CHAP Login Sample Screens

The source code is listed below:

<?xml version="1.0" encoding="utf-8"?>
<Application layout="absolute"
    xmlns="http://www.adobe.com/2006/mxml"
    creationComplete="RPC.initSingleRPC('rpc')">

<Script><![CDATA[
import mx.controls.Alert;
import r1.rpc.RPC;
import r1.rpc.RPCResult;
import r1.rpc.ErrorResult;
import sample.UserVO;

[Bindable] public var user:UserVO;
[Bindable] public var initUsername:String;

private function doRegister():void {
    var regcb:Function = function(id:String, rpcResult:RPCResult):void {
        if (rpcResult is ErrorResult) {
            errorHandler(rpcResult as ErrorResult);
            return;
        }

        Alert.show("User registered successfully.");

        viewStack.enabled = true;
        initUsername = id;
        regUsername.text = regPassword.text = null;
        tabNav.selectedIndex = 1;
    }

    var u:UserVO = new UserVO({ name:regUsername.text, desc:regDesc.text });
    RPC.defaultRPC.registerUser(regcb, null, regPassword.text, u);
}

private function doSignIn():void {
    RPC.defaultRPC.login([this, 'user', errorHandler], username.text, password.text);
    password.text = '';
}

private function doSignOut():void {
    user = null;
    RPC.defaultRPC.logout();
}

private function errorHandler(err:ErrorResult):void {
    Alert.show('[' + err.type + '] ' + err.message);
}
]]></Script>

<TabNavigator id="tabNav" width="100%" height="100%">

    <Form label="Register New User" width="100%">
        <FormItem label="New User Name:">
            <TextInput id="regUsername"/>
        </FormItem>
        <FormItem label="New User Password:">
            <TextInput id="regPassword"/>
        </FormItem>
        <FormItem label="More Information:">
            <TextInput id="regDesc"/>
        </FormItem>
        <FormItem>
            <Button label="Register" click="doRegister()"/>
        </FormItem>
    </Form>

    <ViewStack id="viewStack" label="Login" width="100%" height="100%"
            selectedIndex="{user==null ? 0 : 1}" enabled="false">

        <Form label="Login" width="100%">
            <FormItem label="Name:">
                <TextInput id="username" text="{initUsername}" />
            </FormItem>
            <FormItem label="Password:">
                <TextInput id="password" displayAsPassword="true"/>
            </FormItem>
            <FormItem>
                <Button label="Sign In" click="doSignIn()"/>
            </FormItem>
        </Form>

        <VBox width="100%" height="100%">
            <Label id="caption" text="Welcome, {user.name}!" fontSize="20"/>
            <Spacer height="10"/>
            <Button label="Sign Out" click="doSignOut()"/>
        </VBox>

    </ViewStack>

</TabNavigator>

</Application>

Aspect-Oriented Programming (AOP) for RPC

Remote calls can be intercepted and processed by the R1-RPC/J runtime engine, when the RPCInterceptor has its handler value set to a interceptor class name. An interceptor class must implement r1.rpc.aop.RPCCallInterceptor, which has these methods:

package r1.rpc.aop;

import java.lang.reflect.Method;

public interface RPCCallInterceptor
{
    void preCallHandler(Method method, Object[] params);
    void postCallHandler(Method method, Object returnValue);
    void exceptionHandler(Method method, Throwable t);
}

The @RPCInterceptor annotation applies to both façade class and methods. The following sample façade class have all methods intercepted except for method sub(), which has annotation @RPCInterceptor(handler="none").

package sample;

import r1.rpc.LoginHandler;
import r1.rpc.aop.RPCInterceptor;

@RPCInterceptor(checkAuthentication="yes", handler="sample.MyCallInterceptor")
public class AuthenticatedFriendlyFacade implements r1.rpc.RemoteFacade
{
    public String sayHi() {
        return "Hello, " + ((UserVO)LoginHandler.getUser()).name + '!';
    }

    public int add(int a, int b) {
        return a + b;
    }

    @RPCInterceptor(handler="none")
    public int sub(int a, int b) {
        return a - b;
    }

    public void forceMyException() throws MyException {
        throw new MyException("A custom exception.");
    }
}

The interceptor class sample.MyCallInterceptor simply prints out some verbiage to System.out.

This AOP feature can be useful for special needs, such as profiling, logging, etc.


Cooperative Polling and RPC-Piggyback Messaging Service

R1-RPC/J conveniently supports a client-polling messaging service. Once a messaing callback is registered to an RPC object is established on the client side, any request from the client to the server will bring back any waiting messages for the caller. The way to register a messager listener is like this:

function listener(result:RPCResult):void {
    for each(var msg:Object in result.serverMessages) {
        // process msg ...
    }
}

rpc.addEventListener(RPCResult.RPC_MESSAGE, listener);

On the server side, much like the LoginHandler, you implement the interface for MessageHandler and specify it with the RPCServlet in web.xml; that message handler class can connect with any messaging mechanisms on the server side. The MessageHandler has only one method, getMessages(), that returns an array of valid RPC values such as primitives or VO classes.

The RPC, which is the underlying transport support for any façade classes, has two features designed to facilicate messaging. The first is the ping(). It simply sends an empty request to the server; when there are messages waiting, they will be passed back. The program can call this method to explicitly do a refresh, which can be triggered by a user action such as Button or LinkButton. The second feature is the pollingMaxDelaySeconds property; when set, R1-RPC/J will call ping() if remote accessing has been idle for too long. Note that this is the maximum delay; if some remote method call has happened, the timer is reset because all messages are already piggybacked in the call return.

The messaging support is best demonstrated with a chat application. Please refer to the Chat sample for detail. Because of its nature, this sample is more involved with UI and flow, but the messaging support itself is quite straightforward.


Using R1-RPC/J for Flash RPC

R1-RPC/J client library is pure ActionScript 3, and is tested with Flash CS3. The following is a simple Flash test program that functionally does the same as Simple RPC example mentioned above.

Screenshot of the Flash RPC Sample
Figure 6. Screenshot of the Flash RPC Sample

To create the Flash program using R1-RPC/J, first make R1-RPC/J's classes avaiable to Flash CS3 by copying the r1rpc-?.?.swc library to your Flash CS3 components directory. On Windows XP, it is boot drive\Documents and Settings\username\Local Settings\Application Data\Adobe\Flash CS3\language\Configuration\Components; on Mac OS X, it is Macintosh HD/Users/username/Library/Application Support/Adobe/Flash CS3/language/Configuration/Components. In Flash CS3, open Window|Components and double-click (on Windows) on all R1-RPC/J classes to import them into the library. Drag the used classes (called components in Flash CS3 parlance) onto the stage. Copy the generated façade and VO classes into Flash's class path, and use the following code in the key frame:

import r1.rpc.RPC;
import r1.rpc.RPCResult;
import sample.FriendlyFacade;

RPC.initSingleRPC('rpc');

var facade:FriendlyFacade = new FriendlyFacade();

function sayHiCB(result:String, rpcResult:RPCResult):void {
    if (!rpcResult.success)
        output.text = 'Error: ' + rpcResult.errorDetail.message
    else
        output.text = result
}

input.addEventListener(KeyboardEvent.KEY_DOWN,
    function(e:KeyboardEvent):void {
       if (e.keyCode = Keyboard.ENTER)
           facade.sayHi(sayHiCB, input.text)
    });

stop();

RPC Transport Considerations

As shown in figure 1, the RPC data that goes across the wire is handled by the R1-RPC/J engines on both ends; application programmers do not need to handle nor care about the data format. This format can be controlled by the encoding parameter in the constructor of r1.rpc.RPC or the façade classes. Currently, the data format for transport is defaulted to JSON format; in the future, more efficient binary formats will be implemented when legal concerns and other hurdles are cleared. Atop binary, the data transport can even be encrypted, so that you can effectly use HTTP for secure application transactions.

It would be good if you leave the RPC and façade constructors juse use RPC.RPC_DEFAULT; in the next upgrade of the R1-RPC/J library, the transport may be defaulted to a more efficient format, and you do not need to change a single character of the source code to take advantage of that.


Automated RPC Unit Testing

One of the great advantages of R1-RPC/J is its automated RPC unit testing. An RPC bridge is not only a system component; in a collaborate development environment, it also bridges the client UI team and the server team. The ease of RPC unit testing is invaluable at various phases of team development. Before we discuss the details of how to specify test cases in the façade class, let us first see the automatically generated test application UI.

Screenshot of the RPC Unit Test Application UI
Figure 7. Screenshot of the RPC Unit Test Application UI

Test Case Specification Tags

In a façade class, unit test case for each method is specified via special javadoc tags. There are tags at method- and class-level. These tags are case-insensitive. The following table lists method-level tags.

Test Case Tag Enclosed Tag Description
@ASTest  Used with the @ASTestEnd tag to specify multiple test case attributes using the abbreviated enclosed tags.
@ASTestEnd  The end tag for @ASTest.
@ASTestName @nameThe optional test case name. Default is the method name.
@ASTestInput @inputThe input to the AS3 method. Is an array of parameters.
@ASTestResult @resultThe result checking logic. The variable 'x' holds the return value.
@ASTestNegative @negativeA boolean value, where true indicates this is a negative test.
@ASTestComment @commentThe optional comment.
@ASTestFirst @firstWhen specified with a positive number, this test case will be run prior to other test cases, in the order specified.
@ASTestLast @lastWhen specified with a positive number, this test case will be run after all other test cases, in the order specified.
@ASTestIgnore @ignoreWhen specified, this method will not have test case generated.

Table 2. Unit Testing Method-Level Tags

The most used tags are @ASTestInput and @ASTestResult. If these two are not specified, the test case will not be generated for the method. Another useful tag is @ASTestNegative, which signifies that this is a negative test case that should raise an exception.

Sometimes you want to ignore a test case but like to keep the test case attributes. You can explicitly add a @ASTestIgnore to achieve that.

If many of the test case attributes are specified, you can group them within a pair of @ASTest and @ASTestEnd tags; inside, use the abbreviated forms of the tags (those listed in the Enclosed Tag column in the table above.)

The Generated Test Suite Class

A test suite class is generated for each façade class. The test suite class normally extends r1.rpc.testing.RPCTestSuite; this can be overridden by the façade class-level tag, @ASTestParentClass, in which case the parent class must extend r1.rpc.testing.RPCTestSuite. In any case, you can specify extra code for the test suite class for any test cases to use in the tag @ASTestGlobal. The following table lists all class-level tags:

Test Case Tag Description
@ASTestGlobal The code to be copied verbatim into the test suite class.
@ASTestParentClass The parent class for the test suite class. It must extend r1.rpc.testing.RPCTestSuite.
@ASTestUsername If specified, this is the user name (actually, user ID) for login.
@ASTestPassword The password for login.
@ASTestPasswordEncrypted A boolean value indicating whether RPC should use CHAP or plain-text password for login. Default is true.

Table 3. Unit Testing Class-Level Tags

The following is an exerpt of a Java façade class:

package test;

/**
 *
 * @ASTestGlobal
 *      public static function createVO():MyTestVO {
 *          return new MyTestVO({
 *              valboolean:   true,
 *              valdouble:    8.08,
 *              valfloat:     9.09,
 *              valint:       10,
 *              vallong:      11,
 *              valshort:     12,
 *              valBoolean:   false,
 *              varDouble:    1,
 *              valFloat:     2,
 *              valInteger:   3,
 *              valLong:      4,
 *              valShort:     5,
 *              valString:    "xyz",
 *              varVector:    [ "a" ],
 *              varDate:      new Date(),
 *              valVO:       createVO1()
 *          });
 *      }
 */
public class BasicTestFacade implements r1.rpc.RemoteFacade
{
    /**
     * @ASTestResult x.valint == 3
     */
    public MyTestVO test_VO_out() { return new MyTestVO(); }

    /**
     * @ASTestInput  [ createVO() ]
     * @ASTestResult x.valint == 10
     */
    public MyTestVO test_VO_in(MyTestVO x) { return x; }
}

By running the following command line tool, a corresponding test suite class is generated (list below):

java r1.tools.java2client.ASTestGen -src server_src -dest client_src sample
package test
{
  import r1.rpc.testing.RPCTestSuite;
  import r1.rpc.testing.RPCTestCase;

  public class BasicTestFacadeTestSuite extends RPCTestSuite
  {
    //---------- BEGIN - Custom Global Code ----------
    public static function createVO():MyTestVO {
          return new MyTestVO({
              valboolean:   true,
              valdouble:    8.08,
              valfloat:     9.09,
              valint:       10,
              vallong:      11,
              valshort:     12,
              valBoolean:   false,
              varDouble:    1,
              valFloat:     2,
              valInteger:   3,
              valLong:      4,
              valShort:     5,
              valString:    "xyz",
              varVector:    [ "a" ],
              varDate:      new Date(),
              valVO:       createVO1()
          });
      }

      public static function createVO1():MyTest1VO {
          return new MyTest1VO();
      }
    //---------- END - Custom Global Code ----------

    public function BasicTestFacadeTestSuite() {
      super(new BasicTestFacade);
      var tc:RPCTestCase;

      //--- Test Case: test_VO_out ---
      this.allTestCases.push(tc = new RPCTestCase(this, 'test_VO_out', null, false));
      tc.callFacade = function(facade:BasicTestFacade, cb:Function):void {
        facade.test_VO_out(cb);
      }
      tc.checkResult = function(x:datatype_test.MyTestVO, testCase:RPCTestCase):Boolean {
        return x.valint == 3;
      }

      //--- Test Case: test_VO_in ---
      this.allTestCases.push(tc = new RPCTestCase(this, 'test_VO_in', null, false));
      tc.paramsGenerator=function(testCase:RPCTestCase):Array {
          return [ createVO() ]
      }
      tc.callFacade = function(facade:BasicTestFacade, cb:Function):void {
        var params:Array=this.paramsGenerator(this);
        facade.test_VO_in(cb, params[0]);
      }
      tc.checkResult = function(x:datatype_test.MyTestVO, testCase:RPCTestCase):Boolean {
        return x.valint == 10;
      }
    }
  }
}

The content of the @ASTestInput and @ASTestResult tags are copied into two generated functions, paramsGenerate(testCase:RPCTestCase):Array and checkResult(x:ReturnType, testCase:RPCTestCase):Boolean, respectively. You can write any code for these two tags, as long as at the end they return the appropriate value; in the case of simple expressions, the R1-RPC/J test generation tool adds the return keyword. The parameter testCase can be used in the code. Test suite's data members, such as userObject, are accesible, too.

Control the Order of Test Cases

The @ASTestFirst and @ASTestLast tags allows you to control the order of test case order in the test suite. The test suite executes test cases in the order they are created during batch runs. Both tags take a number; test cases with @ASTestFirst are run before those without this tag, and those with @ASTestLast run after others. If these tags may take a same sequence number for multiple test cases (methods). This can be useful when some test cases depends on the results of other test cases.

Using Login and Test Authenticated Methods

For authenticated façade classes and methods, the test UI will show a Login button instead of the Run All button, unless valid test user name and password are already specified as @ASTestUsername and ASTestPassword in the fa&ccedi;ade class. A successful login operation will keep the returned user object in the property, userObject, which is accessible to all test case methods such as code in @ASTestInput and @ASTestResult. The following is an example.

package sample;

import r1.rpc.LoginHandler;
import r1.rpc.aop.RPCInterceptor;

/**
 * @ASTestUsername test
 * @ASTestPassword test
 * @ASTestPasswordEncrypted false
 *
 * @ASTestGlobal
 *   public function get userVO():UserVO { return userObject as UserVO }
 */
@RPCInterceptor(checkAuthentication="yes")
public class AuthenticatedFriendlyFacade implements r1.rpc.RemoteFacade
{
    /**
     * @ASTestInput
     *   testCase.message = "User logged on with ID: test.";
     *   return [];
     * @ASTestResult
     *   testCase.message = "User name: " + userVO.name;
     *   return x == 'Hello, ' + userVO.name + '!';
     */
    public String sayHi() {
        return "Hello, " + ((UserVO)LoginHandler.getUser()).name + '!';
    }
}

The getter method for userVO also serves the purpose of making sure that class sample.UserVO is linked in; if you just use the generic userObject, a runtime exception may be raised for the missing class of sample.UserVO.

Test User Registration for CHAP Login

When test user name and password are not specified, or they are not valid, the test user need to login. In applications with CHAP login, you may need to register a test user first. Both cases are implemented by the Login and Registration screen below.

Test User Login/Registration
Figure 8. The Test User Login/Registration Screen

When the user types in the name and password and click on the "Register and Login" button, the following function is called in Login.mxml.

private function doRegisterAndLogin():void {
    var userObj:Object = null;
    // If you have more user information for registration,
    // add them here and/or extend the UI for more fields.

    testSuite.registerUserAndLogin(testerUI.postLogin,
        username.text, password.text, userObj);
    doCancel();
}

Note that the r1.rpc.RPC supports registering a user with more information than name and password. If this is the case with your application, you may have to modify Login.mxml to incorporate extra fields for user registration.

Creating Password Digest for Unit Testing with CHAP Login

In the case of CHAP login, only the password digest is stored on the server side. For any given password used in the test suite, a digest must be generated in the user database somehow. The Java program r1.util.CreateDigest does just that. For instance, the password "test" has a digest of "qUqP5cyxm6YcTAhz05Hph5gvu9M=". Stick this text in the user database for user ID of "test", and the above façade tests will work fine, except that @ASTestPasswordEncrypted tag needs be removed.

Creating Custom Unit Test Cases Programmatically

You may want to take advantage of the unit test framework and create more complex unit test cases programmatically; such test cases may not even be façad related. Use classes RPCTestCase and RPCTestSuite in package r1.rpc.testing for this purpose. The best way to start is probably refer to the generated test suite classes. The API documentation is another important source.

The source code of the R1-RPC/J automated test framework UI is available in the package. There are these files:

testsuites.inc
Main.mxml
r1/rpc/testing/ui/TesterUI.mxml
r1/rpc/testing/ui/Login.mxml
r1/rpc/testing/ui/AboutBox.mxml

The testsuites.inc file is generated when all façade classes are processed. An example is as follows:

import test.BasicTestFacadeTestSuite;
import test.DeepTestFacadeTestSuite;
import test.ExoticClassesFacadeTestSuite;
import test.GenTestFacadeTestSuite;

[Bindable] public var testSuites:Array = [
    new BasicTestFacadeTestSuite,
    new DeepTestFacadeTestSuite,
    new ExoticClassesFacadeTestSuite,
    new GenTestFacadeTestSuite
];

[Bindable] public var appTitle:String = 'R1RPC Datatype Testing - RPC Unit Testing';

public var rpcUri:String = '../rpc';

Just add your test suite classes to the testSuites variable, and build the UI. It is that easy.

In the AboutBox.mxml, you can put in informative notes that is useful for the test runners.


Using R1-RPC/J Tools

Using R1-RPC/J Command-Line Code Generation Tools

We have seen the usages of code and test case generation above. These two tools, ASCodeGen and ASTestGen, are an integral part of the R1-RPC/J Toolkit. They are implemented as java doclets for retrieving javadoc tags in the Java source files; they use the Apache Velocity template engine to spit out AS3 source files; therefore, at the time of running the tools, you must have JDK's tools.jar, the Velocity library jar files, and the R1-Tools library file, r1tools.jar, in the classpath.

Both tools share a same set of command-line options with the only exception of -useFacadeInterface for the ASCodeGen tool. If you run either of the tools without parameters, help information is printed:

C:\>java r1.tools.java2client.ASCodeGen

Running ASCodeGen doclet to generate remote facade and VO classes.

usage: java r1.tools.java2client.ASCodeGen
            <Java_Packa>|<Java_Source_Files>
 -subpackages          Optional. When specified, takes package names
                       separated by colon; all sub-packages are included also.
                       An alternative to specifying packages or source file names.
 -exclude              Optional. When specified in the same format as
                       -subpackages, those package are excluded.
 -dest                 base directory for generated test client soure files
 -exceptionOnError     when specified, throws an exception on error (used
                       by the ant task).
 -src                  base directory for Java source files
 -useFacadeInterface   when specified, the generated facade class will
                       implment a generate interface.
 -verbose              when specified, display more information in the process.

The way to specify Java source file is the same as the JDK's javadoc, which is reflected by the -subparckages and -exclude options. The -exceptionOnError flag is used by the Ant tasks, and -useFacadeInterface does not apply to the ASTestGen tool.

Using R1-RPC/J Code Generation Ant Tasks

The R1-RPC/J Toolkit includes good Ant build script support. It contains Ant tasks, <java2as> and <astestgen>, for the two code generation tools, as well as Ant tasks for general Adobe® Flex™ application compilation and deploying.

The included general Flex™ Ant tasks include <mxmlc>, <compc>, <asdoc>, and <htmlwrapper>. We believe our tasks are advantageous over other similar tools on the market, because they all support Ant's <fileset> data type, i.e., you can specify what source files to include and to exclude. Besides, the tasks just use Flex™ SDK tool's command-line arguments and add very few extra, except when it is really desired. For more detailed information reguarding these general Ant tasks, please read ${ant.TOOLS_REF_link}.

All the Ant tasks work with Ant version 1.6.5 or higher. First, copy r1tools.jar and the Velocity template engine library jar files into the lib/ directory of your Ant installation. In your Ant build scripts (usually named build.xml), add a line like this:

<taskdef resource="ant.tasks" classpath="r1tools.jar" />
Task Attribute For Task(s) Description
srcBase bothbase directory for Java source files.
destBase bothbase directory for generated AS3 source files.
packages bothpackage names separated by colon; all sub-packages are included.
verbose botha boolean, indicating to display more information in the process.
useFacadeInterface java2asif true, the generated facade class will implment a generate interface.
testAppTitle astestgenthe RPC unit test application's title.
rpcUri astestgenthe RPC URI used in the RPC unit test application.
<fileset> bothThe source files to include.

Table 4. R1-RPC/J Ant Task Attributes

An example of client code generation is like this:

<java2as destBase="${build.client.src.dir}">
    <fileset dir="${server.src.dir}">
        <include name="**/*.java" />
    </fileset>
</java2as>

The following is an example of RPC unit test case generation:

<astestgen testAppTitle="${test.webapp.title}" rpcUri="../rpc"
        destBase="${build.client.src.dir}">
    <fileset dir="${server.src.dir}">
        <include name="**/*.java" />
    </fileset>
</astestgen>

All the samples in the R1-RPC/J distribution have a build.xml. They are mostly the same with minor changes, for instance, some scripts do not build the unit test application by default. The best way to start a build project is to find a sample that is closest in nature to your project, and clone the build environment for your purpose.



Copyright 2007,2008 RiaOne Company. All Rights Reserved.