Pending State Pattern

When a create, update, delete, or save operation is called on an entity in a message-based synchronization application, the requested change becomes pending. To apply the pending change, call submitPending on the entity, or submitPendingOperations on the mobile business object (MBO) class:

Customer *e = [Customer getInstance];
e.name = @"Fred";
e.address = @"123 Four St.";
[e create]; // create as pending
// Then do this....
[e submitPending]; // submit to server
// ... or this.
[Customer submitPendingOperations]; // submit all pending Customer rows to server

submitPendingOperations submits all the pending records for the entity to the Unwired Server. This method internally invokes the submitPending method on each of the pending records.

The call to submitPending causes a JSON message to be sent to the Unwired Server with the replay method, containing the data for the rows to be created, updated, or deleted. The Unwired Server processes the message and responds with a JSON message with the replayResult method (the Unwired Server accepts the requested operation) or the replayFailure method (the server rejects the requested operation).

If the Unwired Server accepts the requested change, it also sends one or more import messages to the client, containing data for any created, updated, or deleted row that has changed on the Unwired Server as a result of the replay request. These changes are written to the client database and marked as rows that are not pending. When the replayResult message is received, the pending row is removed, and the row remaining in the client database now contains data that has been imported from and validated by the Unwired Server. The Unwired Server may optionally send a log record to the client indicating a successful operation.

If the Unwired Server rejects the requested change, the client receives a replayFailed message, and the entity remains in the pending state, with its replayFailed attribute set to indicate that the change was rejected.

If the Unwired Server rejects the requested change, it also sends one or more log record messages to the client. The SUPLogRecord interface has the following getter methods to access information about the log record:

Method Name Objective-C Type Description
component NSString* Name of the MBO for the row for which this log record was written.
entityKey NSString* String representation of the primary key of the row for which this log record was written.
code int32_t One of several possible HTTP error codes:
  • 200 indicates success.
  • 401 indicates that the client request had invalid credentials, or that authentication failed for some other reason.
  • 403 indicates that the client request had valid credentials, but that the user does not have permission to access the requested resource (package, MBO, or operation).
  • 404 indicates that the client tried to access a nonexistent package or MBO.
  • 405 indicates that there is no valid license to check out for the client.
  • 500 to indicate an unexpected (unspecified) server failure.
message NSString* Descriptive message from the server with the reason for the log record.
operation NSString* The operation (create, update, or delete) that caused the log record to be written.
requestId NSString* The id of the replay message sent by the client that caused this log record to be written.
timestamp NSDate* Date and time of the log record.

If a rejection is received, the application can use the entity method getLogRecords to access the log records and get the reason:

SUPObjectList* logs = [e getLogRecords];
for(id<SUPLogRecord> log in logs)
{
  MBOLogError(@"entity has a log record:\n\
    code = %ld,\n\
    component = %@,\n\
    entityKey = %@,\n\
    level = %ld,\n\
    message = %@,\n\
    operation = %@,\n\
    requestId = %@,\n\
    timestamp = %@",
    [log code],
    [log component],
    [log entityKey],
    [log level],
    [log message],
    [log operation],
    [log requestId],
    [log timestamp]);
}

cancelPendingOperations cancels all the pending records for an entity. This method internally invokes the cancelPending method on each of the pending records.