ColdFusion 9.0 Resources |
Conflict managementConflicts can happen in an offline application when the client modifies data that is already modified on the server. To identify such a conflict, the session.Commit method passes the following data to the ColdFusion server sync method: operations: An array of operations to perform INSERT, UPDATE, or DELETE. clientobjects: An array of new data changes. originalobjects: An array of data that was in the client database before the change. There is no conflict in the following circumstances:
You use the ColdFusion ObjectEquals function to identify conflicts. Pass the function the new instance of cfc from the client and the original instance to check if they are equal. If they are equal, the client has been working with the latest data. If it is not, the server can raise a conflict by returning the sever version of the instance present on the server from the sync method by creating an instance of CFIDE.AIR.conflict.cfc, setting its serverobject property (its only property) to the server value of the data, and returning the array of conflict objects to the AIR client. The following code is an ideal example of sync method that uses ORM methods for syncing operations and also handles conflicts. <cffunction name="sync" returntype="any"> <cfargument name="operations" type="array" required="true"> <cfargument name="clientobjects" type="array" required="true"> <cfargument name="originalobjects" type="array" required="false"> <cfset conclits = ArrayNew(1)> <cfset conflictcount = 1> <cfloop index="i" from="1" to="#ArrayLen( operations )#"> <cfset operation = operations[i]> <cfset clientobject = clientobjects[i]> <cfset originalobject = originalobjects[i]> <cfif operation eq "INSERT"> <cfset obj = ORMGetSession().merge(clientobject)> <cfset EntitySave(obj)> <cfelseif listfindnocase("UPDATE,DELETE",operation) neq 0> <cfset serverobject = EntityLoadByPK("employee",originalobject.getId())> <cfif not isdefined('serverobject') > <cflog text="CONFLICT::SERVER OBJECT NOT FOUND, RECORD MAY BE DELETED ALREADY"> <cfset conflict = CreateObject("component","CFIDE.AIR.conflict")> <cfset conflict.clientobject = clientobject> <cfset conflict.originalobject = originalobject> <cfset conflict.operation = operation> <cfset conflicts[conflictcount++] = conflict> <cfcontinue> </cfif> <cfset isNotConflict = ObjectEquals(originalobject, serverobject)> <cfif isNotConflict> <cfif operation eq "UPDATE"> <cfset obj = ORMGetSession().merge(clientobject)> <cfset EntitySave(obj)> <cfelseif operation eq "DELETE"> <cfset obj = ORMGetSession().merge(originalobject)> <cfset EntityDelete(obj)> </cfif> <cfelse><!----Conflict---> <cflog text = "is a conflict"> <cfset conflict = CreateObject("component","CFIDE.AIR.conflict")> <cfset conflict.serverobject = serverobject> <cfset conflict.clientobject = clientobject> <cfset conflict.originalobject = originalobject> <cfset conflict.operation = operation> <cfset conflicts[conflictcount++] = conflict> <cfcontinue> </cfif> </cfif> </cfloop> <cfif conflictcount gt 1> <cfreturn conflicts> </cfif> </cffunction> The CFC handling of the conflict depends on your application. In some cases, it can be appropriate to ignore the conflict and update the server data source with the new client data. In many cases, as in the preceding example, the CFC informs the client about the conflict by returning the server value of the data. On the client side, you use code such as the following to register the method that handles the conflict that the server returns. syncmanager.addEventListener(ConflictEvent.CONFLICT, conflictHandler); function conflictHandler(event:ConflictEvent):void { var conflicts:ArrayCollection = event.result as ArrayCollection; var token:SessionToken = session.keepAllServerObjects(conflicts); token.addResponder(new mx.rpc.Responder(conflictSuccess, conflictFault)); } The conflictevent object contains an array of conflict objects that contain the clientinstance, originalinstance and the serverinstance. To accept the server's data, the application calls keepAllServerObjects, which takes an ArrayCollection that was passed to the conflict handler, or call the keepServerObject that takes an individual Conflict instance as shown in the following code. This conflict handler simply accepts any returned server object. function conflictHandler(event:ConflictEvent):void { var conflicts:ArrayCollection = event.result as ArrayCollection; var conflict:Conflict = conflicts.getItemAt(0); var token:SessionToken = session.keepServerObject(conflict); token.addResponder(new mx.rpc.Responder(conflictSuccess, conflictFault)); } Conflicts can happen in the following cases:
Note: After a commit or conflict resolution, it is recommended to
synchronize the client database with the server data source, because
the server can have new data available from other clients.
ActionScript has a few reserved keywords. When you name the Class/SQLite table, ensure that you do not use any of the reserved keywords. For example, Order is an ActionScript reserved keyword. If you name a table or class as Order, the table creation fails. To avoid this name conflict, use the [Table(name="OrderTable")] metadata tag to override the default name. Your code for the Order.as class could look something like the following: package test { [Entity] [Table(name="OrderTable")] public class Order { public function Order() { } [Id] public var oid:uint; public var name:String; [ManyToMany(targetEntity="test::Product",cascadeType='ALL')] public var products:Array; } } |