End-to-End Rapid Application Development with Data Services & Adobe Flex

The simplest way to explain Flex Data Services (FDS) is to compare them with Flex Remoting. Simply put, FDS addresses only a subset of operations facilitated via Flex Remoting - result set requests. However, whereas Flex Remoting enables one-way requests, FDS combines one-way requests with the publish/subscribe mechanism so that besides the original result set FDS sends the client live updates produced by other clients of the same destination. And there's one more dimension in which Data Services depart from Flex Remoting - support for hierarchical collections, but we won't be covering that subject in this book.

In other words, FDS resolves the task of programming data collaboration: Several users may edit different rows and columns of the "same" DataGrid and see each other's changes automatically pushed by the server. Now, what if they overlap each other's work? In terms of FDS that's called a conflict and the FDS API provides for flexible conflict resolution, which may require the user's intervention.

An FDS destination can be configured for working with the data that is persisted to a data store as well as supporting scenarios that persist the data in the server's memory. To that end, FDS provides Java and ActionScript data adapters that are responsible for reading and updating a persistent data store according to its type. In this chapter we'll focus on use cases involving Java adapters.

Flex Data Services & Automation: Problem Statement & Solution
Robust in enabling collaborative manipulation of data, FDS demands a substantial development effort in case of persistent data stores. In particular, you need to build:

We just mentioned four classes/files containing hard-coded names of the fields and there are more. To function properly, these hard-coded values have to be kept in sync, which is an additional maintenance effort whenever the data structures change.

Addressing this complexity, the main idea of this chapter is not to cover every twist of the FDS API, but rather automate the development effort that FDS takes for granted. We'll start with a "manual," albeit simplified, example of using DataServices. Then we'll introduce you to the methodology of complete code generation based on the pre-written XSL templates and FDS-friendly XML metadata, which will be extracted from the annotated Java abstract classes.

This methodology is fully implemented in DAOFlex - an Open Source utility that's a complementary addition to this book. We'll gradually introduce this tool by leading you through a process of creating the most comprehensive template that generates a complete DataServices Data Access Object DAO. Finally, we'll show you how to run and customize DAOFlex in your development environment so that writing and synchronizing routine DataServices support classes becomes a task of the Ant building tool and not yours!

A "Manual" FDS Application
Let's handcraft the application presented in Figure 6.1. This application displays a Panel with a scrollable DataGrid that we consciously did not size in the horizontal dimension, so that all columns can be viewed without shrinking. The database result set is ultimately produced by the following SQL query that will use a bound variable in place of the question mark:

    select * from employee where start_date < ?

There are two buttons below the DataGrid: Fill and Commit. As the names imply, these buttons pull the original data from the database table and submit the data changes back to an FDS destination. A separate Parameters panel permits entering parameters of the back-end method behind the Fill button, which, in our case, is the employee start date :

Building the Client Application
Let's build the client application first. The full listing of the application is presented in Listing 6.1. We start with defining the mx:DataServices object (a k a ds), which points to the destination "Employee." Later, when we get to the server components we'll discuss mapping this destination to the backing Java class:

    <mx:DataService id="ds" destination="Employee" fault="onFault(event)" />

We provide only a rudimentary handler of the fault event that's sufficient to keep us aware of any anomalies that may occur along the way. Dynamic referencing fault and faultString properties will spare us from casting to a specific event:

    private function onFault(evt:Event):void {
       Alert.show(evt["fault"]["faultString"], "Fault");
    }

Then we define a handler of the application's onCreationComplete event where we instantiate a collection to be eventually associated with our mx:DataService object and, most importantly, set both autoCommit and autoSyncEnabled of the ds to false:

    private function onCreationComplete() : void {
       collection = new ArrayCollection();
       ds.autoCommit=false;
       ds.autoSyncEnabled=false;
    }

By setting autoCommit to false we state that all updates have to be batched and explicitly submitted to the server as a single transaction during the ds.commit() call. By setting autoSyncEnabled to false we effectively protect our local instance of data from delivery of messages caused by other clients connected to destination "Employee." Setting autoSyncEnabled to false is entirely optional, and we use it to avoid dealing with application specific conflict resolution. In particular, in the handler of the Commit button's click event you might uncomment the first line to support the "optimistic" way of handling the conflicts:

    private function commit_onClick():void {
       //ds.conflicts.acceptAllClient();
       // Optimistic conflict handling, as oppose to
       ds.conflicts.acceptAllServer();
       ds.commit();
    }

Last, we have to initiate the population of the local collection with the ds.fill() method, which we do inside the click event handler of the button Fill:

    private function fill_onClick():void {
       ds.release();
       ds.fill(collection, param_getEmployees_startDate.selectedDate);
    }

The scripting portion of the application is completed so let's build the UI. We create a DataGrid with the dataProvider bound to our collection in Listing 6.1. For brevity's sake, we didn't list all the columns here: you'll have a chance to scrutinize them in the subsequent section of this chapter.

The DataGrid and ControlBar with Fill and Commit buttons are put inside a Panel, with DataGrid's title bearing the name of the destination and a specific getEmployees method of that destination, which will ultimately be invoked during the ds.fill() call. The second panel, titled Parameters, contains a form with a single item mx:DateField. Both panels are embraced by the VDividedBox.

We've included a linkage variable of the data transfer type to ensure that the corresponding ActionScript class (EmployeeDTO) will be linked into the generated SWF file.

<?xml version="1.0" encoding="UTF-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#FFFFFF"
creationComplete="onCreationComplete()">
   <mx:DataService id="ds" destination="Employee" fault="onFault(event)" />
   <mx:VDividedBox width="800" height="100%">
    <mx:Panel title="Employee::getEmployees()" width="800" height="70%">
     <mx:DataGrid id="dg" dataProvider="{collection}" editable="true" height="100%">
      <mx:columns><mx:Array>
       <mx:DataGridColumn dataField="EMP_ID" headerText="Emp Id" />
       <mx:DataGridColumn dataField="MANAGER_ID" headerText="Manager Id" />
       <mx:DataGridColumn dataField="EMP_FNAME" headerText="Emp Fname" />
       . . . .
      </mx:Array></mx:columns>
     </mx:DataGrid>
     <mx:ControlBar>
      <mx:Button label="Fill" click="fill_onClick()"/>
      <mx:Button label="Commit" click="commit_onClick()"        enabled="{ds.commitRequired}"/>
     </mx:ControlBar>
    </mx:Panel>
    <mx:Panel title="Parameters" width="100%" height="30%">
     <mx:HBox height="100%" width="100%">
      <mx:Form label="getEmployees()">
       <mx:FormItem label="startDate:">
        <mx:DateField id="param_getEmployees_startDate" selectedDate="{new Date()}"/>
       </mx:FormItem>
      </mx:Form>
     </mx:HBox>
   </mx:Panel>
</mx:VDividedBox>
<mx:Script>
   <![CDATA[
   import mx.controls.Alert;
   import mx.collections.ArrayCollection;
   import com.theriabook.datasource.dto.EmployeeDTO;
   private var linkage:com.theriabook.datasource.dto.EmployeeDTO = null;
   [Bindable]
   private var collection : ArrayCollection;
   private function fill_onClick():void {
     ds.release();
     ds.fill(collection, param_getEmployees_startDate.selectedDate);
   }
   private function onCreationComplete() : void {
     collection = new ArrayCollection();
     ds.autoCommit=false;
     ds.autoSyncEnabled=false;
   }
   private function commit_onClick():void {
     ds.conflicts.acceptAllClient();
     ds.commit();
   }
   private function onFault(evt:Event):void {
     Alert.show(evt["fault"]["faultString"], "Fault");
   }

   ]]>
</mx:Script>
</mx:Application>


The application above doesn't cover all use cases of the FDS API. We tried to keep it as small as possible for one reason: to enable metadata-based code generation. Ultimately, it will be entirely up to you which code you'd elect to generate by modifying the DAOFlex templates. Finally, we present the listing of the ActionScript class EmployeeDTO that our collection uses in communicating with the Employee destination:

package com.theriabook.datasource.dto
{

    [Managed]
    [RemoteClass(alias="com.theriabook.datasource.dto.EmployeeDTO")]
    public dynamic class EmployeeDTO
    {

       // Properties
       public var EMP_ID : Number;
       public var MANAGER_ID : Number;
       public var EMP_FNAME : String;
       public var EMP_LNAME : String;
       public var DEPT_ID : Number;
       public var STREET : String;
       public var CITY : String;
       public var STATE : String;
       public var ZIP_CODE : String;
       public var PHONE : String;
       public var STATUS : String;
       public var SS_NUMBER : String;
       public var SALARY : Number;
       public var START_DATE : Date;
       public var TERMINATION_DATE : Date;
       public var BIRTH_DATE : Date;
       public var BENE_HEALTH_INS : String;
       public var BENE_LIFE_INS : String;
       public var BENE_DAY_CARE : String;
       public var SEX : String;

       public function EmployeeDTO() {
       }
    } //EmployeeDTO
}

Creating Assembler & DTO Classes
The time has come to work on the Java side, which is a rather tedious process, so we'll gradually go top-down.

Our first stop is an Assembler class that the FDS Employee destination should map to. As the Flex documentation suggests, you can implement the methods on your Assembler class in several ways:

We'll take an XML approach that lets us declare a so-called sync-method. The XML contract of the destination's sync-method prescribes that it accepts a single parameter: a List of flex.data.ChangeObject elements. We find it convenient to control how we want to process data changes. In particular, we'd like to maintain the following order: all deletes, then all updates, and then all inserts. After all, if the user deletes a record for an employee with EMP_ID= 123 and then inserts a new record with EMP_ID=123, we certainly wouldn't want our sync-method to issue the INSERT, followed by DELETE FROM employee WHERE EMP_ID=123 during the batched FDS data modifications.

Let's keep in mind that our ultimate focus is the metadata-based code generation. Should you decide to have your Assemblers as descendants of the AbstractAssembler, you'd have the liberty of modifying the corresponding DAOFlex template.

Listing 6.3 presents the complete XML describing the destination Employee. Under the default configuration scenario, this XML would go inside the <services> node of the flex-data-services.xml file, located in the WEB-INF/lib/flex folder of your Web application.

We set com.theriabook.datasource.EmployeeAssembler as the exact name of the class mapped by our destination, with the methods java.util.List getEmployees_fill(java.util.Date dt) and the List getEmployees_sync(List lst) acting as the fill and sync methods respectively.

Even though XML doesn't explicitly declare that that the fill-method returns a List or that the sync-method takes a List, this is a part of the XML contract for Assembler classes in destinations:

<destination id="Employee">
    <adapter ref="java-dao"/>
<properties>
    <source>com.theriabook.datasource.EmployeeAssembler</source>
    <scope>application</scope>
    <metadata>
      <identity property="EMP_ID"/>
    </metadata>
<network>
<session-timeout>0</session-timeout>
<paging enabled="false"/>
<throttle-inbound policy="ERROR" max-frequency="500"/>
<throttle-outbound policy="ERROR" max-frequency="500"/>
</network>
<server>
    <fill-method>
    <name>getEmployees_fill</name>
    <params>java.util.Date</params>
    </fill-method>
    <sync-method>
      <name>getEmployees_sync</name>
    </sync-method>
</server>
</properties>
</destination>

The structure of the EmployeeAssembler Java class is pretty straightforward. This class delegates the actual data retrieval and update of the data store to EmployeeDataServiceDAO class, which we'll discuss next:

package com.theriabook.datasource;
import java.util.*;

public final class EmployeeAssembler
{
    public EmployeeAssembler()
    { }

    public final List getEmployees_fill(Date startDate) {
      return new EmployeeDataServiceDAO().getEmployees(startDate);
    }

    public final List getEmployees_sync(List items) {
      return new EmployeeDataServiceDAO().getEmployees_sync(items);
    }

}

The chapter then leads the reader line by line through the entire set of programming tasks required to build a fully functional create-retrieve-update-delete (CRUD) application based on Employee Data Service destination. Once the job is well-know, the reader is invited to automate it once and for all. Here is one more fragment.

Introducing Metadata
Let's look at the snippet from the XML file generated by the DAOFlex utility - Employee.xml. Please note the name of the Java package - com.theriabook.datasource, the name of the Assembler's fill method - getEmployees(), names on the transferring structures on the Java and ActionsScript side, both pointing to array of com.theriabook.dto.EmployeeDTO objects, the name of the connection pool - jdbc/theriabook, and the name of the method's parameter - startDate:

<?xml version="1.0" encoding="UTF-8"?>
<WEBSERVICE NAME="Employee" PACKAGE="com.theriabook.datasource" TYPE="DAOFlex" >
<SERVER LANGUAGE="Java" MODE="JEE">
<SQL ACTION="SELECT"
NAME="getEmployees" POOL="jdbc/theriabook" SCOPE="public"
ASTYPE="com.theriabook.dto.EmployeeDTO[]" JAVATYPE="com.theriabook.
dto.EmployeeDTO[]"
>
<PARAM IN="Y" INDEX="1" JAVATYPE="Date" NAME="startDate"/>
</SQL>
</SERVER>
</WEBSERVICE>

Starting at this point, we'll be working our way through this XML while building the complete XSL stylesheet from scratch. Once we make this stylesheet, it'll be capable to manufacture any DataServiceEmployeeDAO, DataServiceDepartmentDAO, etc. - as long as we have the metadata XMLs like the above one. You're probably wondering at this point: "What's the input of the DAOFlex that allows it to generate this XML?"

The input for DAOFlex is an annotated Java class, like the one presented in Listing 6.11:

package com.theriabook.datasource;
import java.util.Date;
import java.util.List;
/**
* @DAOFlex:webservice pool=jdbc/theriabook
*/
public abstract class Employee {
/**
* @DAOFlex:sql
* sql=select * from employee where start_date < :startDate or start_date=:start_Date
* transferType=com.theriabook.dto.EmployeeDTO[]
* updateTable=employee
* keyColumns=emp_id
*/
public abstract List getEmployees(Date startDate);
}

Reference

  • Rich Internet Applications with Adobe Flex and Java: www.riabook.com
  • © 2008 SYS-CON Media