Data Access Automation

Once the transport technology has been selected, you need to try to remove the complexity of the data access and persistence layer. The Data Management Services that come with LCDS provide an excellent model for automation of this task. But you can develop your own framework based on the open source products, and in the following sections, you’ll learn how to re-create all the necessary components for a data persistence framework.

To offer functionality similar to that of LCDS in our framework, we need to create the following data management components:

  • Data transfer objects

  • ChangeObject

  • Assembler

  • A change-tracking collection

  • A destination-aware collection

Note

In the following sections, we’ll offer you Farata Systems’ version of such components. If you like them, get their source code in the CVS repository at SourceForge and use them as you see fit. We also encourage you to enhance them and make them available for others in the same code repository.

Data Transfer Objects

Using data transfer objects (DTOs) is very important for architecting automated updates and synchronization. In Flex/Java RIA, there are at least two parties that need to have an “exchange currency”: ActionScript and Java. Each of these parties has their own contracts on how to support the data persistence. Let’s concentrate on the ActionScript part first.

In the Café Townsend sample, the data objects responsible for the exchange between Java and ActionScript are EmployeDTO.java and EmployeeDTO.as (see a fragment of EmployeeDTO.as in Example 6-2). The Java side sends instances of EmployeDTO objects, which are automatically re-created as their ActionScript peers on the frontend.

Example 6-2. Employee.DTO.as

/* Generated by Clear Data Builder (ActionScriptDTO_IManaged.xsl) */
package com.farata.datasource.dto
{
    import flash.events.EventDispatcher;
    import flash.utils.Dictionary;
    import flash.utils.ByteArray;
    import mx.events.PropertyChangeEvent;
    import mx.core.IUID;
    import mx.utils.UIDUtil;

    [RemoteClass(alias="com.farata.datasource.dto.EmployeeDTO")]
    [Bindable(event="propertyChange")]
    public dynamic class EmployeeDTO extends EventDispatcher //implements IManaged
    {
            // Internals
            public var _nulls:String;

            // Properties
        private var _EMP_ID : Number;
        private var _MANAGER_ID : Number;
        ...
            public function get EMP_ID() : Number{
                    return _EMP_ID;
            }
            public function set EMP_ID( value : Number ):void{
                    var oldValue:Object = this._EMP_ID;
                    if (oldValue !== value)   {
                            this._EMP_ID = value;
                            dispatchUpdateEvent("EMP_ID", oldValue, value);
                    }
            }

            public function get MANAGER_ID() : Number{
                    return _MANAGER_ID;
            }
            public function set MANAGER_ID( value : Number ):void{
                    var oldValue:Object = this._MANAGER_ID;
                    if (oldValue !== value)   {
                            this._MANAGER_ID = value;
                            dispatchUpdateEvent("MANAGER_ID", oldValue, value);
                    }
            }

 
            public function get properties():Dictionary {                   
                    var properties:Dictionary = new Dictionary();   
                    properties["EMP_ID"] = _EMP_ID;
                    properties["MANAGER_ID"] = _MANAGER_ID;

                        return properties;
                }

            public function set properties(properties:Dictionary):void {                    
        
             _EMP_ID = properties["EMP_ID"];
             _MANAGER_ID = properties["MANAGER_ID"];
             ... 
            }
                
            private var _uid:String;
            public function get uid():String
            {
                    return _uid;
            }
            public function set uid(value:String):void
            {
                    _uid = value;
            }

            public function EmployeeDTO() {
                    _uid = UIDUtil.createUID();
            }

            public function newInstance() : * { return new EmployeeDTO();}

            private function dispatchUpdateEvent(propertyName:String,
                                                 oldValue:Object, value:Object):void {
                dispatchEvent(
                    PropertyChangeEvent.createUpdateEvent(this, propertyName,
                                                          oldValue, value)
                );                      
            }
              
            public function clone(): EmployeeDTO {
                var x:EmployeeDTO = new com.farata.datasource.dto.EmployeeDTO();
                x.properties = this.properties;
                return x;
            }
    } 
}

The class starts with a [RemoteClass] metadata tag that instructs the compiler that this class should be marshaled and re-created as its peer com.farata.datasource.dto.EmployeeDTO on the server side.

This class is an event dispatcher and any changes to its members will result in the update event, which allows you to perform easy tracking of its properties’ changes by dispatching appropriate events. This feature is also important for the UI updates if the DTOs are bound to UI controls, such as a DataGrid.

Note that all the properties in this class are getter/setter pairs: they can’t remain public variables, because we want the dispatchUpdateEvent() method to be called every time the variable’s value is being changed.

In addition to the functional properties like EMP_ID and EMP_FNAME, the class also contains a setter and getter for the uid property; this qualifies the class as an implementer of the IUID interface. Existence of a uid property allows easy indexing and searching of records on the client.

However, implementing uid as a primary key on the server side is crucial in order to ensure synchronization and uniqueness of updates. Usually uid represents the primary key from a database table. The other function often required by automatic persistence algorithms is getChangedPropertyNames(), in order to teach DTO to mark updated properties (Example 6-3).

Example 6-3. EmployeeDTO.java

package com.farata.datasource.dto;
import java.io.Serializable;
import com.farata.remoting.ChangeSupport;
import java.util.*;
import flex.messaging.util.UUIDUtils;

public class EmployeeDTO implements Serializable, ChangeSupport {

 private static final long serialVersionUID = 1L;
 public String _nulls; // internals
 public long EMP_ID;
 public long MANAGER_ID;
 ...
 public Map getProperties() {
 HashMap map =  new HashMap();
 map.put("EMP_ID", new Long(EMP_ID));
 map.put("MANAGER_ID", new Long(MANAGER_ID));
...
 return map;
}

// Alias names is used by code generator of CDB in the situations
// if select with aliases is used, i.e.
// SELECT from A,B  a.customer cust1, b.customer cust2

// In this case plain names on the result set would be cust1 and cust2,
// which would complicate generation of the UPDATE statement.
// If you don't use code generators, there is no need to add aliasMap
// to your DTOs
public static HashMap aliasMap =  new HashMap();

public String getUnaliasedName(String name) {
 String result = (String) aliasMap.get(name);
 if (result==null)
  result = name;

return result;
}

public String[] getChangedPropertyNames(Object o) {
 Vector  v =  new Vector();
 EmployeeDTO old = (EmployeeDTO)o;
 if (EMP_ID != old.EMP_ID)
     v.add(getUnaliasedName("EMP_ID"));

 if (MANAGER_ID != old.MANAGER_ID)
     v.add(getUnaliasedName("MANAGER_ID"));
 ...
 String [] _sa = new String[v.size()];
 return (String[])v.toArray(_sa);
}
}

To better understand how changes are kept, take a look at the internals of the ChangeObject class, which stores all modifications performed on the DTO. It travels between the client and the server.

ChangeObject

ChangeObject is a special DTO that is used to propagate the changes between the server and the client. The ChangeObject class exists in the Data Management Services of LCDS, and is shown in Example 6-4. On the client side, it is just a simple storage container for original and new versions of a record that is undergoing some changes. For example, if the user changes some data in a DataGrid row, the instance of the ChangeObject will be created, and the previous version of the DTO that represents this row will be stored along with the new one.

Example 6-4. ChangeObject.as

package  com.farata.remoting {
   [RemoteClass(alias="com.farata.remoting.ChangeObjectImpl")]
   public class ChangeObject {

      public var state:int;
      public var newVersion:Object = null;
      public var previousVersion:Object = null;
      public var error:String = "";
      public var changedPropertyNames:Array= null;

      public static const UPDATE:int=2;
      public static const DELETE:int=3;
      public static const CREATE:int=1;

       public function ChangeObject(state:int=0,
             newVersion:Object=null, previousVersion:Object = null) {
          this.state = state;
          this.newVersion = newVersion;
          this.previousVersion = previousVersion;
       }

       public function isCreate():Boolean {
          return state==ChangeObject.CREATE;
       }
       public function isUpdate():Boolean {
          return state==ChangeObject.UPDATE;
       }
       public function isDelete():Boolean {
          return state==ChangeObject.DELETE;
       }
   }
}

As you can see, every changed record can be in a DELETE, UPDATE, or CREATE state. The original version of the object is stored in the previousVersion property and the current one is in the newVersion. That turns the ChangeObject into a lightweight implementation of the Assembler pattern, which offers a simple API to process all the data changes in a standard way, similar to what’s done in the Data Management Services that come with LCDS.

The Java counterpart of the ChangeObject (Example 6-5) should have few extra convenience generic methods. All specifics are implemented in a standard way in the EmployeeDTO.

Example 6-5. ChangeObjectImpl.java

Package com.theriabook.remoting;
import java.util.*;
public class ChangeObjectImpl {
   public void fail() {
      state = 100;
   }
   public void fail(String desc) {
      // TODO Auto-generated method stub
      fail();
        error = desc;
   }
   public String[] getChangedPropertyNames() {
      // TODO Auto-generated method stub
      changedNames = newVersion.getChangedPropertyNames(previousVersion);
      return changedNames;
   }
    public Map getChangedValues()
    {
       if ((newVersion==null) || (previousVersion==null)) return null;
        if(changedValues == null)
        {
            if(changedNames == null)
               changedNames = getChangedPropertyNames();
            if (newMap == null)
               newMap = newVersion.getProperties();
            changedValues = new HashMap();
            for(int i = 0; i < changedNames.length; i++)
            {
                String field = changedNames[i];
                changedValues.put(field, newMap.get( field));
            }
      }
        return Collections.unmodifiableMap(changedValues);
    }
   public Object getPreviousValue(String field) {
      if (previousMap == null)
         previousMap = previousVersion.getProperties();
      return previousMap.get( field );
   }
   public boolean isCreate() {
      return state == 1;
   }
   public boolean isDelete() {
      return state == 3;
   }
   public boolean isUpdate() {
      return state == 2;
   }
public void setChangedPropertyNames(String [] columns)
    {
       changedNames = columns;
       changedValues = null;
    }
public void setError(String s) {
       error = s;
    }
   public void setNewVersion(Object nv) {
      newVersion = (ChangeSupport)nv;
        changedValues = null;
   }
   public void setPreviousVersion(Object o) {
      previousVersion = (ChangeSupport)o;
   }
   public void setState(int s) {
      state = s;
   }

//---------------------- E X T E N S I O N S--------------------------
   public int state = 0;
   public ChangeSupport newVersion = null;
   public ChangeSupport previousVersion = null;
   public String error ="";

   protected Map newMap = null;
   protected Map previousMap = null;
   protected String[] changedNames = null;
   protected Map changedValues = null;
}

Assembler and DAO Classes

In Core J2EE Patterns, the Transfer Object Assembler means a class that can build DTOs from different data sources (see http://java.sun.com/blueprints/corej2eepatterns/Patterns/TransferObjectAssembler.html). In Flex/Java RIA, the Assembler class would hide from the Flex client actual data sources used for data retrieval. For example, it can expose the method getEmployees() for retrieval of the EmployeeDTO objects that are actually retrieved from more than one data source.

For simplicity, the method getEmployees() shown in Example 6-6 delegates the processing to a single Data Access Object (DAO), but this does not have to be the case, and the data required for population of the list of EmployeeDTOs can be coming from several data sources.

Similarly, for data updates the client calls the sync() method without knowing the specifics; the DAO class or classes take care of the data persistence.

In the example framework, you’ll build an Assembler class similar to what Adobe recommends creating in the case of using LCDS. The instances of ChangeObject are used for communication between Flex and the Java Assembler class, which in turn will use them for communication with DAO classes.

The Assembler pattern cleanly separates the generic Assembler’s APIs from specifics of the DAO implementation.

Example 6-6. EmployeeAssembler.java

package com.farata.datasource;

import java.util.*;

public final class EmployeeAssembler{
        public EmployeeAssembler(){
        }

        public  List getEmployees() throws Exception{
                return new EmployeeDAO().getEmployees();
        }

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

The two main entry points (data retrieval and updates) will show you how easy it is to build a DAO adapter.

First, you need to separate the task into the DAO and Assembler layers by introducing methods with fill (retrieve) and sync (update) functionality. The complete source code of the EmployeeDAO class is included in the code samples accompanying this book, and the relevant fragments from this class follow in Example 6-7.

Example 6-7. Fill and sync fragment from EmployeeDAO.java

package com.farata.datasource;
import java.sql.*;
import java.util.*;
import flex.data.*;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.transaction.*;
import com.farata.daoflex.*;

public final class EmployeeDAO extends Employee {

   public final List getEmployees_sync(List items)    {
      Coonection conn = null;
      try  {
         conn = JDBCConnection.getConnection("jdbc/test");
         ChangeObject co = null;
         for (int state=3;  state > 0; state--) { //DELETE, UPDATE, CREATE
              Iterator iterator = items.iterator();
                 while (iterator.hasNext()) {   // Proceed to all updates next
                      co = (ChangeObject)iterator.next();
                      if(co.state == state && co.isUpdate())
                          doUpdate_getEmployees(conn, co);
               if(co.state == state && co.isDelete())
                          doDelete_getEmployees(conn, co);
               if(co.state == state && co.isCreate())
                          doCreate_getEmployees(conn, co);
            }
         }
      } catch(DataSyncException dse) {
              dse.printStackTrace();
              throw dse;
      } catch(Throwable te) {
            te.printStackTrace();
            throw new DAOException(te.getMessage(), te);
      } finally {
         JDBCConnection.releaseConnection(conn);
      }
         return items;
    }
  public final List /*com.farata.datasource.dto.EmployeeDTO[]*/
                                                getEmployees_fill() {

        String sql = "select * from employee    where dept_id=100";
        ArrayList list = new ArrayList();
        ResultSet rs = null;
        PreparedStatement stmt = null;
        Connection conn = null;
        try   {
            conn = JDBCConnection.getConnection("jdbc/test");
            stmt = conn.prepareStatement(sql);
            rs = stmt.executeQuery();
            StringBuffer nulls = new StringBuffer(256);
            while( rs.next() )    {
              EmployeeDTO dto = new dto.EmployeeDTO();
              dto.EMP_ID = rs.getLong("EMP_ID");
              if( rs.wasNull() ) { nulls.append("EMP_ID|"); }
              dto.MANAGER_ID = rs.getLong("MANAGER_ID");
              if( rs.wasNull() ) { nulls.append("MANAGER_ID|"); }
              ...
              dto.uid = "|" +   dto.EMP_ID;
              list.add(dto);
            }
            return list;
        } catch(Throwable te) {
            te.printStackTrace();
            throw new DAOException(te);
        } finally {
            try {rs.close(); rs = null;} catch (Exception e){}
            try {stmt.close(); stmt = null;} catch (Exception e){}
            JDBCConnection.releaseConnection(conn);
    }   }

As you can see in Example 6-7, the implementation of the fill method is really straightforward. Review the code of the sync method, and you’ll see that it iterates through the collection of ChangeObjects; calls their methods isCreate(), isUpdate(), and isDelete(); and calls the corresponding function in the DAO class. These functions are shown in the example.

Implementation of the insert and delete statements is based on new or old versions wrapped inside ChangeObject. Example 6-8 calls the method getNewVersion() to get the data for insertion in the database and getPreviousVersion() for delete.

Example 6-8. Create and delete fragment from EmployeeDAO.java

  private ChangeObject doCreate_getEmployees(Connection conn,
           ChangeObject co) throws SQLException{

        PreparedStatement stmt = null;
        try {
            String sql = "INSERT INTO EMPLOYEE " +
              "(EMP_ID,MANAGER_ID,EMP_FNAME,EMP_LNAME,
              DEPT_ID,STREET,CITY,STATE,ZIP_CODE,PHONE,
              STATUS,SS_NUMBER,SALARY,START_DATE,TERMINATION_DATE,
              BIRTH_DATE,BENE_HEALTH_INS,BENE_LIFE_INS,
              BENE_DAY_CARE,SEX)"+
              " values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";

            stmt = conn.prepareStatement(sql);
            EmployeeDTO item = (EmployeeDTO) co.getNewVersion();
            stmt.setLong(1, item.EMP_ID);
            stmt.setLong(2, item.MANAGER_ID);
            ...
            if (stmt.executeUpdate()==0)
               throw new DAOException("Failed inserting.");
            co.setNewVersion(item);
            return co;
        } finally {
            try { if( stmt!=null) stmt.close(); stmt = null;}
            catch (Exception e){// exception processing goes here}
    }   }

    private void doDelete_getEmployees(Connection conn, ChangeObject co)
                                          throws SQLException{
        PreparedStatement stmt = null;
        try {
            StringBuffer sql = new StringBuffer
                        ("DELETE FROM EMPLOYEE WHERE (EMP_ID=?)");
            EmployeeDTO item = (EmployeeDTO) co.getPreviousVersion();
            stmt = conn.prepareStatement(sql.toString());
            stmt.setLong(1, item.EMP_ID);

            if (stmt.executeUpdate()==0)
                throw new DataSyncException(co, null,
                   Arrays.asList(new String[]{"EMP_ID"}));
            } finally {
            try { if( stmt!=null) stmt.close(); stmt = null;
            } catch (Exception e){}
    }   }

To form the update statement, you need both the previous and the new versions of the data available inside ChangeObject instances (Example 6-9).

Example 6-9. Update fragment from EmployeeDAO.java

private void doUpdate_getEmployees(Connection conn, ChangeObject co)
                                                throws SQLException{

      String updatableColumns ",EMP_ID,MANAGER_ID,EMP_FNAME,EMP_LNAME,
             DEPT_ID,STREET,CITY,STATE,ZIP_CODE,
             PHONE,STATUS,SS_NUMBER,SALARY,START_DATE,
             TERMINATION_DATE,BIRTH_DATE,BENE_HEALTH_INS,
             BENE_LIFE_INS,BENE_DAY_CARE,SEX,";

        PreparedStatement stmt = null;

        try {
            StringBuffer sql = new StringBuffer("UPDATE EMPLOYEE SET ");
            EmployeeDTO oldItem =
                      (EmployeeDTO) co.getPreviousVersion();
            String [] names = co.getChangedPropertyNames();
            if (names.length==0) return;

            for (int ii=0; ii < names.length; ii++) {
               if (updatableColumns.indexOf("," + names[ii] +",")>=0)
                    sql.append((ii!=0?", ":"") + names[ii] +" = ? ");
            }

            sql.append( " WHERE (EMP_ID=?)" );
            stmt = conn.prepareStatement(sql.toString());

            Map values = co.getChangedValues();
            int ii, _jj;
            Object o;
            _jj = 0;

            for (ii=0; ii <    names.length; ii++) {
               if (updatableColumns.indexOf("," + names[ii] +",")>=0) {
                  _jj++;
                  o =  values.get(names[ii]);
                    if ( o instanceof java.util.Date)
                       stmt.setObject(
             _jj,DateTimeConversion.toSqlTimestamp((java.util.Date)o) );
                    else
                        stmt.setObject( _jj, o );
                }
            }

            _jj++;
            stmt.setLong(_jj++, oldItem.EMP_ID);

            if (stmt.executeUpdate()==0)
                throw new DataSyncException(co, null,
                         Arrays.asList(new String[]{"EMP_ID"}));
        } finally {
            try { if( stmt!=null) stmt.close(); stmt = null;
            } catch (Exception e){}
         }    }

}

You can either manually write the code shown in Examples 6-2 to 6-9, or use the Clear Data Builder for automated code generation.

The code in the examples is generic and can be either generated for the best performance or parameterized for Java frameworks such as Spring or Hibernate.

DataCollection Class

It’s time to establish an ActionScript collection that will have two important features:

  • It will know how to keep track of changes to its data.

  • It will be destination-aware.

Such a collection would keep track of the data changes made from the UI. For example, a user modifies the data in a DataGrid that has a collection of some objects used as a data provider. You want to make a standard Flex ArrayCollection a little smarter so that it’ll automatically create and maintain a collection of ChangeObject instances for every modified, new, and deleted row.

We’ve developed a class DataCollection that will do exactly this seamlessly for the application developer. This collection also encapsulates all communications with the server side via RemoteObject, and it knows how to notify other users about the changes made by you if they are working with the same data at the same time.

Shown in Example 6-10, this collection stores its data in the property source, the array of ChangeObjects in modified, and the name of the remote destination in destination. Every time the data in the underlying collection changes, this collection catches the COLLECTION_CHANGE event, and based on the event’s property kind (remove, update, add) removes or modifies the data in the collection. To support undo functionality, all modified objects are stored in the properties deleted and modified.

Example 6-10. DataCollection.as—take 1

package com.farata.collections {
   [Event(name="propertyChange", type="mx.events.PropertyChangeEvent")]
   [Bindable(event="propertyChange")]

   public class DataCollection extends ArrayCollection {

  public var destination:String=null;
      protected var ro:RemoteObject = null;
      public var deleted:Array = new Array();
      public var modified:Dictionary = new Dictionary();
      public var alertOnFault:Boolean=true;
      private var trackChanges:Boolean=true;

      // The underlying data of the ArrayCollection
      override public function set source(s:Array):void {
      super.source = s;
       list.addEventListener(CollectionEvent.COLLECTION_CHANGE,
                                               onCollectionEvent);
          resetState();
          refresh();
    }
       // collection's data changed
   private function onCollectionEvent(event:CollectionEvent) :void {
         if (!trackChanges) return;
         switch(event.kind) {
         case "remove":
            for (var i:int = 0; i < event.items.length; i++) {
             var item:Object = event.items[i];
                var evt:DynamicEvent = new DynamicEvent("itemTracking");
                evt.item = item;
                dispatchEvent(evt);
                if (evt.isDefaultPrevented()) break;
                var co:ChangeObject = ChangeObject(modified[item]);
                var originalItem:Object=null;
            if (co == null) {
               // NotModified
                   originalItem = item;
               } else if (!co.isCreate()) {
                  // Modified
                   originalItem = co.previousVersion;
                   delete modified[item];
                   modifiedCount--;
                } else {
                   // NewModified
                   delete modified[item];
                   modifiedCount--;
                }
                if (originalItem!=null) {
               deleted.push(originalItem);
               deletedCount = deleted.length;
                };
             }
          break;
          case "add":
             for ( i = 0; i < event.items.length; i++) {
                item = event.items[i];
                evt = new DynamicEvent("itemTracking");
                evt.item = item;
                dispatchEvent(evt);
                if (evt.isDefaultPrevented()) break;
                modified[item] = new ChangeObject
                         (ChangeObject.CREATE, cloneItem(item), null);
            modifiedCount++;
             }
             break;
          case "update":
             for (i = 0; i < event.items.length; i++) {
                item = null;
            var pce:PropertyChangeEvent =
                           event.items[i] as PropertyChangeEvent;
                if ( pce != null) {
                   item = pce.currentTarget; //as DTO;
                   if( item==null )
                      item = pce.source;
                   evt = new DynamicEvent("itemTracking");
                   evt.item = item;
                   dispatchEvent(evt);
                   if (evt.isDefaultPrevented()) break;
                }
                if (item != null) {
                   if(modified[item] == null) {
                      if (item.hasOwnProperty("properties")) {
                         var oldProperties:Dictionary =
                                                   item["properties"];
                         oldProperties[pce.property] = pce.oldValue;
                     var previousVersion:Object = cloneItem(item,
                                                               oldProperties)
                      } else {
                         previousVersion = ObjectUtil.copy(item);
                         previousVersion[pce.property] = pce.oldValue;
                      }
                      modified[item] = new ChangeObject(ChangeObject.UPDATE,
                                             item, previousVersion);
                     modifiedCount++;
                      }
                      co = ChangeObject(modified[item]);
                      if (co.changedPropertyNames == null) {
                         co.changedPropertyNames = [];
                      }
                      for (  i = 0; i < co.changedPropertyNames.length; i++ )
                         if (  co.changedPropertyNames[i] == pce.property)
                            break;
                      if ( i >= co.changedPropertyNames.length)
                         co.changedPropertyNames.push(pce.property);
                   }
                }

                break;

          }
            // to be continued
       }

For our DataCollection to really be useful for developers, it has to offer an API for querying and manipulating its state. Developers should be able to query the collection to find out whether this particular object is new, updated, or removed. The modified variable of DataCollection is a reference to ChangeObject’s, and each ChangeObject instance can “introduce” itself as new, updated, or removed. Hence we are adding the methods listed in Example 6-11 to the DataCollection.

Example 6-11. Adding more methods to DataCollection

   public function isItemNew(item:Object):Boolean {
      var co: ChangeObject = modified[item] as ChangeObject;
      return (co!=null && co.isCreate());
   }
   public function setItemNew(item:Object):void {
      var co: ChangeObject = modified[item] as ChangeObject;
      if (co!=null){
         co.state = ChangeObject.CREATE;
      }
   }
   public function isItemModified(item:Object):Boolean {
      var co: ChangeObject = modified[item] as ChangeObject;
      return (co!=null && !co.isCreate());
   }
   public function setItemNotModified(item:Object):void {
      var co: ChangeObject = modified[item] as ChangeObject;
      if (co!=null) {
         delete modified[item];
            modifiedCount--;
      }
   }

   private var _deletedCount : int = 0;
    public function get deletedCount():uint {
      return _deletedCount;
   }

    public function set deletedCount(val:uint):void {
      var oldValue :uint = _deletedCount ;
      _deletedCount = val;
      commitRequired = (_modifiedCount>0 || deletedCount>0);
      dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "deletedCount",
                                                     oldValue, _deletedCount));
   }

   private var _modifiedCount : int = 0;
   public function get modifiedCount():uint {
      return _modifiedCount;
   }
   public function set modifiedCount(val:uint ) : void{
      var oldValue :uint = _modifiedCount ;
      _modifiedCount = val;
      commitRequired = (_modifiedCount>0 || deletedCount>0);
      dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "modifiedCount",
                                                     oldValue, _modifiedCount));
      }

     private var _commitRequired:Boolean = false;
     public function set commitRequired(val :Boolean) :void {
        if (val!==_commitRequired) {
         _commitRequired = val;
         dispatchEvent(PropertyChangeEvent.createUpdateEvent(this,
                          "commitRequired", !_commitRequired, _commitRequired));
        }
     }
     public function get commitRequired() :Boolean {
        return _commitRequired;
     }

   public function resetState():void {
      deleted = new Array();
      modified = new Dictionary();
      modifiedCount = 0;
      deletedCount = 0;
   }

The DataCollection can “tell” if any of its objects are new, removed, or updated; keeps the counts of modified and deleted objects; and knows if a commit (saving changes) is required.

All the changes are accessible as the properties deletes, inserts, and updates. The property changes will get you the entire collection of the ChangeObjects (Example 6-12).

Example 6-12. Adding more properties to DataCollection

public function get changes():Array {
   var args:Array = deletes;
   for ( var item:Object in modified) {
      var co: ChangeObject =
         ChangeObject(modified[item]);
      co.newVersion = cloneItem(item);
      args.push(co);
   }
     return args;
  }

  public function get deletes():Array {
   var args:Array = [];
   for ( var i :int = 0; i < deleted.length; i++) {
      args.push(
         new ChangeObject(
            ChangeObject.DELETE, null,
               ObjectUtils.cloneItem(deleted[i])
         )
      );
   }
   return args;
  }
  public function get inserts():Array {
   var args:Array = [];
   for ( var item:Object in modified) {
      var co: ChangeObject = ChangeObject(modified[item]);
      if (co.isCreate()) {
         co.newVersion = ObjectUtils.cloneItem(item);
         args.push( co );
      }
   }
   return args;
  }
  public function get updates():Array {
   var args:Array = [];
   for ( var item:Object in modified) {
      var co: ChangeObject = ChangeObject(modified[item]);
      if (!co.isCreate()) {
            // make up to date clone of the item
         co.newVersion = ObjectUtils.cloneItem(item);
         args.push( co );
      }
   }
     return args;
  }

This collection should also take care of the communication with the server and call the fill() and sync() methods. Because the DataCollection internally uses Flex remoting, it’ll create the instance of the RemoteObject with result and fault handlers.

The application developer will just need to create an instance of DataCollection, then specify the name of the remote destination and the remote method to call for data retrieval and update.

As you saw in Example 1-27:

collection = new DataCollection();
collection.destination="com.farata.Employee";
collection.method="getEmployees";
...
collection.fill();

The fill() method here invokes the remote method getEmployees(). If the sync() method is not specified, its default name will be getEmployees_sync(). After the code fragment in Example 6-13 is added to DataCollection, it’ll be able to invoke a remote object on the server after creating the instance of RemoteObject in the method createRemoteobject(). The method fill() calls invoke(), which in turn creates an instance of the remote method using getOperation() on the remote object.

Example 6-13. Adding destination awareness to DataCollection

   public var _method : String = null;
   public var syncMethod : String = null;

   public function set method (newMethod:String):void {
      _method = newMethod;
      if (syncMethod==null)
         syncMethod = newMethod + "_sync";
   }
   public function get method():String {   return _method;      }

   protected function createRemoteObject():RemoteObject {
      var ro:RemoteObject = null;
      if( destination==null || destination.length==0 )
         throw new Error("No destination specified");

      ro = new RemoteObject();
      ro.destination    = destination;
      ro.concurrency    = "last";
      ro.addEventListener(ResultEvent.RESULT, ro_onResult);
      ro.addEventListener(FaultEvent.FAULT,   ro_onFault);
      return ro;
   }

   public function fill(... args): AsyncToken {
      var act:AsyncToken = invoke(method, args);
      act.method = "fill";
      return act;
   }

   protected function invoke(method:String, args:Array):AsyncToken {
      if( ro==null ) ro = createRemoteObject();
      ro.showBusyCursor = true;
      var operation:AbstractOperation = ro.getOperation(method);
      operation.arguments = args;
      var act:AsyncToken = operation.send();
      return act;
   }

   protected function ro_onFault(evt:FaultEvent):void {
          CursorManager.removeBusyCursor();
      if (evt.token.method == "sync") {
         modified = evt.token.modified;
         modifiedCount = evt.token.modifiedCount;
         deleted = evt.token.deleted;
      }
      dispatchEvent(evt);
      if( alertOnFault && !evt.isDefaultPrevented() ) {
         var dst:String = evt.message.destination;
         if( dst==null || (dst!=null && dst.length==0) )
         try{ dst = evt.target.destination; } catch(e:*){};

         var ue:UnhandledError = UnhandledError.create(null, evt,
               DataCollection, this, evt.fault.faultString,
                 "Error on destination: " + dst);
         ue.report();
      }
   }


   public function sync():AsyncToken {
      var act:AsyncToken = invoke(syncMethod, [changes]);
      act.method = "sync";
      act.modified = modified;
      act.deleted = deleted;
      act.modifiedCount=modifiedCount;
      return act;
   }

   }
}

Let’s recap what you’ve done. You subclassed ArrayCollection and created the DataCollection class that remembers all the changes to the underlying collection in the form of ChangeObject instances. Each ChangeObject “knows” if it’s there because the user modified, removed, or added a new object to the collection. The DataCollection internally creates a RemoteObject based on the name of the destination and calls the sync() method, passing the collection of ChangeObjects to it for persistence on the server. Data retrieval is performed by calling DataCollection.fill().

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.160.43