The AIR integration feature introduced in ColdFusion 9 has an ActionScript ORM for persisting entities in the SQLite database present within Adobe Integrated Runtime (AIR). This release has the following enhancements for this ActionScript ORM:

  • Support for auto-generating primary keys
  • Support for encrypted database (introduced in AIR 1.5).
  • Cache file used by ActionScript ORM to track the operations on SQLite database is now in the applicationStoragedirectory instead of applicationDirectory. You can specify the location of the cahceDirectory in openSession API on syncmanager.
  • Supports Self Join relationships for one-to-one, one-to-many, and many-to-many database relationships.
  • Supports both Array and ArrayCollection for use in ActionScript Entity to represent a collection in a database relationship.
  • ActionScript ORM logs all the SQL statements that ORM uses to persist entities into the SQLite database.
  • New APIs keepClientObject and keepAllClientObjects to ensure that the server updates are not retained when ColdFusion server raises conflict.
  • The class SessionToken is dynamic and therefore, data can be stored on the token returned from the ORM APIs.
  • Supports autocommit mode

Auto-generating primary keys

This release supports primary key generation for the ActionScript ORM using the metadata tag GeneratedValue.

GeneratedValue
Description

Adding this tag on an ActionScript primary key file auto-generates primary key.

Parameters

Parameter

Description

strategy

UUID uses the Flash UUID API to generate the ID (used for primary key of type string) or INCREMENT (for primary key of type int).

initialValue

Applies only for INCREMENT strategy. Specifies the initial value of the primary key. The default value is 0.

incrementBy

Applies only for INCREMENT strategy. Specifies the integer with which the value must be incremented to generate the primary key.

If the ID value is not present in the object, the value is generated and is assigned the primary key value. If the key value is already present on the object instance, then the key generation is ignored.For integer primary keys, the database table is checked for the presence of existing primary keys. If the highest key value is greater than the initialValue, then the key that is generated next will be an increment of the highest key value. For example, if the initialValue that you specify is 1, and the database (already) has a key value 5, then the next key is generated with the value 6 (5+1, if incrementBy is set to 1).

Example

//Integer Primary Keys
===========
package test.apollo.CFSQLiteSupport.INCREMENTPK
{
[Entity]
[RemoteClass(alias="Customer")]
public class Customer
{
public function Customer()
{
}
[Id]
[GeneratedValue(strategy="INCREMENT",initialValue=5,incrementBy=2) ]
public var cid:int;
public var name:String;

[OneToOne(mappedBy="customer")]
public var ord:Order;
}
}
//String Primary Keys
===========
package test.apollo.CFSQLiteSupport.UUIDPK
{
[Entity]
[RemoteClass(alias="Customer")]
public class Customer
{
public function Customer()
{
}
[Id]
[GeneratedValue(strategy="UUID") ]
public var cid:String;
public var name:String;

[OneToOne(mappedBy="customer")]
public var ord:Order;
}
}

Encrypting the database

You can protect the database used by ActionScript ORM with an encryption key.
Use the ByteArray encryption key for syncmanager.openSession method to encrypt the database. The user-specified database file and the cache database file (used by the ActionScript ORM) are both encrypted using the encryption key you specify.
The key is optional.
Example

dbFile = File.userDirectory.resolvePath("customerManger.db");
dbDir = File.applicationStorageDirectory;
var keyGenerator:EncryptionKeyGenerator = new EncryptionKeyGenerator();
var encryptionKey:ByteArray = keyGenerator.getEncryptionKey("UserPassword");

var sessiontoken:SessionToken =syncmanager.openSession(dbFile,179176,encryptionKey,dbDir);

For details on EncryptionKeyGenerator, see the section Using the EncryptionKeyGenerator class to obtain a secure encryption key in Developing Adobe AIR 1.5 Applications with Flex.

Specifying the cache directory

The cache directory where you store the cache file can be specified using the cacheDirectory (instance of flash.filesystem.File) for the syncmanager.openSession method.
The cacheDirectory is optional.

Note: By default, the cache file used by the ActionScript ORM is stored in the File.applicationStorageDirectory (in ColdFusion 9, it was stored in File.applicationDirectory).

For example, see Encrypting the database in this page.

Support for self joins

Database table can be related to itself through a foreign key. A typical example is an Employee table with a manager relationship containing the employee id of the managers (who manage the employee). 
The manager id refers to another row in the same table. This is an example of one-to-one self join.
There can be one-to-many self join and many-to-many self joins with an intermediate join table.
ColdFusion 9 Update 1 has self join support for all the relationships in the ActionScript ORM.
The following ActionScript class definition for customer entity illustrates how all the self-join relationships are defined:

package
{

[Bindable]
[RemoteClass(alias="AIRIntegration.customer")]
[Entity]
public class Customer
{
[Id]
[GeneratedValue(strategy="INCREMENT",initialValue=5,incrementBy=2)]
public var cid:int;
public var name:String;

[OneToOne(cascadeType='ALL',fetchType="EAGER")]
[JoinColumn(name="add_id",referencedColumnName="aid")]
public var address:Address;

// Many-to-One self Join
[ManyToOne(targetEntity="onetoone::Customer",fetchType="EAGER")]
[JoinColumn(name="managerId",referencedColumnName="cid")]
public var manager:Customer;

// One-to-one Self Join
[OneToOne(targetEntity="onetoone::Customer",fetchType="EAGER")]
[JoinColumn(name="spouseId",referencedColumnName="cid",unique="true")]
public var spouse:Customer;

// Many-to-Many self Join
[ManyToMany(targetEntity="onetoone::Customer",fetchType="EAGER")]
[JoinTable(name="CUSTOMER_PARENTS_MAPPINGS")]
[JoinColumn(name="CUST_ID",referencedColumnName="cid")]
[InverseJoinColumn(name="PARENT_ID",referencedColumnName="cid")]
public var parents:Array;


// Many-to-Many self Join
[ManyToMany(targetEntity="onetoone::Customer",fetchType="EAGER")]
[JoinTable(name="CUSTOMER_CHILDREN_MAPPINGS")]
[JoinColumn(name="CUST_ID",referencedColumnName="cid")]
[InverseJoinColumn(name="CHILD_ID",referencedColumnName="cid")]
public var children:Array;
[OneToMany(targetEntity="onetoone::Order",cascadeType='REMOVE',mappedBy="customer",fetchType="EAGER")]
public var orders:Array;

}
}

ArrayCollection to hold multiple entities

In addition to Array, you can now use ArrayCollection to hold multiple entities in a database relationship. ArrayCollection can also be used in the ActionScript entities as Arrays are used to represent the related entities.
Example

package
{
import mx.collections.ArrayCollection;

[Bindable]
[RemoteClass(alias="AIRIntegration.customer")]
[Entity]
public class Customer
{
[Id]
[GeneratedValue(strategy="INCREMENT",initialValue=5,incrementBy=2)]
public var cid:int;
public var name:String;

[OneToOne(cascadeType='ALL',fetchType="EAGER")]
[JoinColumn(name="add_id",referencedColumnName="aid")]
public var address:Address;
[OneToMany(targetEntity="onetoone::Order",cascadeType='REMOVE',mappedBy="customer",fetchType="EAGER")]
public var orders:ArrayCollection;

}
}

Server-side configuration

See the section Changes in the XML configuration files for Flash Remoting in ColdFusion 9 and ColdFusion 9.0.1.

Logging SQL statements

The ActionScript ORM logs all SQL statements that it executes. 
The log can be configured as follows:

  1. Add a log target for the AIR applications as shown in the following example:

    var logTarget:TraceTarget = new TraceTarget();
    logTarget.filters = "*";
    logTarget.level = LogEventLevel.ALL;
    Log.addTarget(logTarget);

The log target is the TraceTarget where all the trace statements appear. The log target can be set to any other log using the Flash APIs.

ColdFusion ActionScript APIs

The following two APIs have been introduced to the session class in the coldfusion.air package:

keepAllClientObjects
Description

Takes an ArrayCollection of conflict instances and keeps the client object for every conflict instance in the ArrayCollection.

Returns

An instance of coldfusion.air.SessionToken (which is the token for keepAllClientObjects call).

Syntax

public function keepAllClientObjects(conflicts:ArrayCollection):SessionToken

Parameters

Parameter

Description

mx.collections.ArrayCollection

An ArrayCollection of conflicts raised by the server.

Example

private function conflictHandler(event:ConflictEvent):void
{
// Alert.show("Server returned a Conflict !");
var conflicts:ArrayCollection = event.result as ArrayCollection;
// Ignore Server data and retain client Data in SQLite DB
var token:SessionToken = session.keepAllClientObjects(conflicts);
token.addResponder(new mx.rpc.Responder(conflictSuccess, conflictFault));
}

keepClientObject
Description

Ensures that the client object is retained instead of the one from the server (despite server raising data conflict).The API also ensures that the retained client object is not sent to the server as a new operation on sync.

Returns

An instance of coldfusion.air.SessionToken associated with keepClientObject call.

Syntax

public function keepClientObject(conflict:coldfusion.air.Conflict):SessionToken

Parameters

Parameter

Description

coldfusion.air.Conflict

The conflict that the server raises.

Example

See the example in the section keepAllClientObjects. For keepClientObject, the only difference is that you must iterate over each conflict in the conflictarray collection.

Offline AIR SQLite API enhancements

The following new parameters for openSession:

New Parameters

Type

Required/Optional

Description

encryptionKey

ByteArray

Optional

Used to encrypt offline SQLite database. For details, see Encrypting the database in this page.

cacehDirectory

File

Optional

Used to specify a custom cache directory. For details, see Specifying the cache directory in this page.

SessionToken class is dynamic

A class is dynamic in ActionScript if you can add additional key-value pairs to the instance of the class.
In this release, sessionToken is dynamic class. Therefore, you can add additional information that can be passed from where the API is called to the success or fault handlers.
Example

private function fetchData():void
{
var token:AsyncToken= syncmanager.fetch("fetch");
token.addResponder(new mx.rpc.Responder(fetchSuccess, fetchFault));

// Test For SessionToken class to be Dynamic, so that Dynamic Properties could be added
token.userdefined_key = "value";
}
public function fetchSuccess(event:SyncResultEvent):void
{

if(event.token.userdefined_key == "value")
{ .... }
}

Support for AutoCommit

SyncManager supports a Boolean property autoCommit. 
The default value is false.
If true, the changes in the local database are committed to the server when the save, saveUpdate, and remove methods are used as shown here:

private var syncmanager:SyncManager = new SyncManager();
syncmanager.autoCommit = true;

This functionality helps you minimize the conflicts during the synchronization with the server, particularly in the case of auto-generation of primary key on client and serverside.

New attribute for SessionResultEvent and SessionToken

The classes SessionResultEvent and SessionToken have a new attribute autoGeneratedId that gets populated with the auto-generated ID used by ActionScript ORM. autoGeneratedId is populated only when a key is generated by the ActionScript ORM in that specific call.

Example

private function connectSuccess(event:SessionResultEvent):void
{
// Generate an Order Obect
.
.
.
.
// Save the Order
var savetoken:SessionToken = session.save(ord);
savetoken.addResponder(new mx.rpc.Responder(savesuccess, savefault));
}
private function savesuccess(event:SessionResultEvent):void
{
// This is how, you can access autogenrated PK
RememberINTPK = event.autoGeneratedId.toString();
var loadtoken:SessionToken = session.loadByPK(Order,{oid:RememberINTPK},true);
loadtoken.addResponder(new mx.rpc.Responder(loadsuccess,loadfailure))
}

Note: Assume that the server database generates primary keys and you choose to generate primary key on client SQL Lite table (as shown in the example). This scenario results in a conflict which the application developer must resolve. An option is to design your application in such a way that you minimize conflicts between client and server objects. In this case, you can set client object primary keys as null or empty string before saving data to the database server using serverside ORM EntitySave function.

This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License  Twitter™ and Facebook posts are not covered under the terms of Creative Commons.

Legal Notices   |   Online Privacy Policy