The following principles apply to optimistic checkout:
Optimistic checkout enables concurrent changes to the same User view . Any number of callers (such as workflows) could checkout the same User view optimistically. Each caller could concurrently modify the same User view. Each caller would succeed in reprovisioning the user as long as the caller's desired changes do not conflict with intervening changes made by any and all other callers.
Optimistic checkout does not prevent pessimistic checkout. An optimistic checkout of a User view is not a shared lock. Specifically, an optimistic checkout of a User view would not prevent another caller from checking out the same User view pessimistically.
Pessimistic checkout takes precedence over optimistic checkout. A pessimistic checkout of a User view would still prevent any other user from checking out the same User view pessimistically. The existence of a pessimistic update user task would also prevent another any other caller from reprovisioning the same user since the pessimistic name-based task workflow lock remains in effect.
However, once the caller who makes a pessimistic checkout of a User view checks in the same User view or otherwise abandons that caller's lock on the User repository object and that task terminates, any other caller who checked out the same User view optimistically may succeed in reprovisioning the user as long as the second caller's changes do not conflict with the changes that the first caller made.
Optimistic tasks can launch but wait (with configurable retry interval and count) until a running pessimistic task completes before computing conflicts and attempting reprovisioning. Only one pessimistic task may launch at one time as guaranteed by the existing task name based workflow lock.
Checkouts are pessimistic by default. Pessimistic checkout remains the default behavior of the Waveset product for any installation of Waveset that is upgraded to a release that supports optimistic locking. This preserves backward-compatibility for older deployments.
Waveset detects conflicts. A conflict is defined as an overlap between two sets of changes produced by the existing UpdateViewer difference calculation. For example, suppose that a caller calls checkoutView("User:joebob") optimistically. Our caller locally modifies waveset.email, and then calls checkinView(). If no other caller has modified waveset.email in that same User view since the point in time that our caller made his optimistic checkout, then our caller's checkinView() should succeed.
However, suppose that a caller changes a field within an existing account for an Exchange Server resource. Within the User view, the caller modifies accounts[ExchangeServer].Profile. As long as no other caller has modified that same field (since the point in time that our caller made his optimistic checkout), then the caller's checkinView() should succeed.
The system merges changes. If no conflicts are detected, then the changes made to the user are merged into the current User View. In this way, both our caller's changes and intervening changes to the User View are preserved.
Optimistic reprovisioning attempts can be retried. At the point in the Update User workflow where reprovisioning the User object begins, if optimistic checkout is enabled, the workflow attempts to lock the User repository object. It also tries to ensure that no pessimistic Update User workflows are executing concurrently. If either of these conditions cannot be met then, by default, the optimistic workflow sleeps briefly then retries. Both the retry interval and retry count values are configurable view options.
Reprovisioning can be forced. In the case that conflicts are detected, an error result is returned from the call to WorkflowServices#reProvision(). The list of conflicts is returned in a format similar to the change objects currently calculated by the UpdateViewer and includes the local new value, remote new value, and the original value for each conflict. The default Update User workflow detects this condition and terminates, thereby abandoning the changes.
You can customize a workflow to allow for some type of remediation. A caller may wish to force the changes to be committed after the remediation process. Setting the view option OP_IGNORE_CONFLICTS to “true” at view checkin time enables this behavior. If it is set and evaluates to boolean true when WorkflowServices.reProvision() is called, then conflict checking is performed. In addition, Waveset merges the data, despite any detected conflicts. However, during the merge process where conflicts do occur (either in single-valued attributes or for individual elements in lists of named GenericObjects), the local change will be committed, thus overwriting any such intervening changes. Non-conflicting intervening changes are preserved. The caller assumes complete responsibility for the possibility of overwriting such conflicting intervening changes. However it should be noted that conflicts are specific to a particular point in time. Once an initial call to reProvision() returns a conflict error and releases the user object lock, other tasks may make intervening changes before the second call to force reprovisioning can occur.
Conflict detection is based on the existing UpdateViewer change detection code. This code collects changes to the user view for each account assigned to the user. It also detects changes to a set of hardcoded attributes in the waveset view and also to extended attributes. The list of hardcoded updatable waveset attributes includes:
Constants.ACCOUNT_ID Constants.PASSWORD Constants.EMAIL "disabled" "locked" "lockExpiry" "questionLocked" "questionLockExpiry" "correlationKey" "passwordExpiry" "passwordExpiryWarning" "organization" "roleInfos" "applications" "resources" "resourceAssignments" "exclusions" "assignedLhPolicy" "adminRoles" "capabilities" "controlledOrganizations" "userForm" "viewUserForm" "customForm" "forwardingApprover" "questions" "suppliedQuestions" "idmManager" "delegates"
In the Administrator interface, such changes to these User view attributes are detected by the existing UpdateViewer and presented to the administrator for confirmation when the Save button is pressed. It appears as a list of attribute names with corresponding old and new values.
Conflict detection for optimistic checkout is based on the UpdateViewer update view and proceeds as follows:
The list of local changes is fetched from the view. The UpdateViewer has already calculated this change list, and it is present in the User view.
The list of remote changes is computed by fetching the baseline view stored in the view at checkout time. These changes are compared to a fresh view based on the current repository User object in the same manner as the local changes are computed. A similar change list is produced.
If a changed attribute appears in either the local or remote changes but not both, then no conflict is indicated.
If a changed attribute appears in both the local and remote changes and has the same new value in both, then no conflict is indicated. This comparison is straightforward for attributes whose values are not lists. However, attributes that contain lists are processed differently, depending on the type of list.
The following types of lists need to be considered when checking for conflicts:
Lists composed of elements which are not named GenericObjects never produce a conflict. Since element-level list conflict detection is predicated on the ability to identify list elements (by name) and no such name exists in this case, no conflicts can occur - list order is not significant. As such, for such lists, conflict detection is bypassed. Merging, however, still occurs at the element level.
For example, consider an attribute whose value is a list of strings and whose initial value is [“A”, “B”, “C”]. Then two edits (such as two UserView checkout() methods) occur with this as the starting state for this attribute value. The first edit removes element B, resulting in a final list value of [“A”, “C”] and commits that change. The second edit (an optimistic edit) starts with the same initial value of the list ([“A”, “B”, “C”] but then removes element “A” and adds element “D” giving a local end result attribute value of [“B”, “C”, “D”]. The final merged list value is [“C”, “D”] because:
The first edit removed “B”.
The second edit removed “A”.
The second edit added “D”.
Assuming named GenericObjects with the same name represent the same object allows for better and more granular conflict detection. Changes in a named element are identifiable since list elements with the same name are assumed to be the same object. In this case, a failing equality comparison indicates a conflict. List additions and deletions are also similarly discernible.
For lists of named GenericObjects, Waveset can detect whether an element has been added, deleted or modified in both the local and remote changes. The system then compares the resulting two lists to determine if a conflict is indicated using the following rules:
Table 11–1 Local and Remote Conflict Checks
Local Add |
Local Delete |
Local Change |
|
---|---|---|---|
Remote Add |
Add conflict is not equal | ||
Remote Delete |
No |
Yes |
|
Remote Change |
Yes |
Conflict if not equal |
The blank cells in the table indicate that such a case is not possible. Since the baseline view is the same for computing both local and remote changes, a list addition in one set of changes indicates that the value was not present in the baseline. Therefore, it is not possible to delete or change this (non-existent) element value in the local or remote changes. Similarly, the deletion of an element or change to an element value indicates that the value was present, and an addition of that value is not possible.
An example of such a list that contains named GenericObject elements is the roleinfos attribute. Both the old and new values are lists of objects which are named by the role name. An addition or deletion of such a named element can be determined by querying the list by object name. For cases where the named element is present in the list in both the local and remote changes, the (Generic) objects can be compared for equality. If the new values are identical (equal), then this is not considered a conflict.
The section provides information about the Waveset objects that are used to implement optimistic checkouts.
The UserViewConstants.java file defines the following options to support optimistic checkouts. These can be set as view options when the User View is checked out in the same manner as existing view options.
OP_OPTIMISTIC - If this option is set to True, optimistic checkout semantics are enabled. This option must be set at view checkout time and at view checkin time so that a baseline copy of the view can be taken and optimistic reprovisioning can occur. This baseline copy of the view is used during the subsequent view checkin to determine any intervening changes that may have occurred to the same User by other Update User workflow reprovisioning operations. These intervening changes, if any, are compared against the local changes to determine if any conflicts are present and, if not, to merge the local changes with the intervening remote changes to complete the reprovisioning process for that User.
OP_OPTIMISTIC_RETRY_COUNT - The number of times a caller can to retry a reprovisioning operation. The default value is 3 times.
The reprovisioning operation attempts to lock the User repository object and ensures that no pessimistic Update User workflow is running for the same User before performing optimistic reprovisioning. If either of these conditions are not met, the task retries after sleeping for the specified interval. If the retry count is exceeded, an exception is thrown.
OP_OPTIMISTIC_RETRY_INTERVAL - The number of milliseconds to wait between reprovisioning retries. The default value is 30,000 milliseconds (30 seconds).
OP_IGNORE_CONFLICTS - This view option can be passed at user view checkin time to force the local changes to be committed. If it is set and evaluates to True, then the reprovisioning behaves in an optimistic manner with the exception that conflicts, if found, are not considered an error. Merging is still performed with local change values being committed in cases of conflict, thus overwriting these conflicting intervening changes. Non-conflicting changes are preserved. In the case where no other errors occurred but conflicts were found and OP_IGNORE_CONFLICTS is set to true, the WavesetResult will contain a named result (called conflicts) but will not have an error result set.
The following example enables optimistic checkout in a JSP (such as account/modify.jsp):
try { String id = requestState.getParameter(requestState, "id"); if (id == null) { form.setTitle(Messages.UI_ACCT_CRT_USR_TITLE); form.setViewId("User"); } else { form.setTitle(Messages.UI_ACCT_EDIT_USR_TITLE); form.setViewId("User:" + id); // Set this option so that checkin of the view // won't launch a reprovision. This does however mean that the user // changes will be stored. Need to be able to defer this. // requestState.setOption(UserViewConstants.OP_NO_REPROVISION, "true"); requestState.setOption(UserViewConstants.OP_SELECT_RESOURCES, "true"); // Enable Optimistic Checkout requestState.setOption(UserViewConstants.OP_OPTIMISTIC, "true"); // Set this options to true to build the // "Forwarding Approvers" select list req.setOption(UserViewConstants.OP_BUILD_APPROVER_LISTS, "true"); }
Conflicts, if any, are returned in the WavesetResult returned by WorkflowServices#reProvision() in a named GenericObject called conflicts. This is normally further returned in the GenericObject result from the call to view checkin. This object contains an attribute named accounts which is a list, by account, of attribute conflicts for that account.
For each such conflict detected, a GenericObject representing the conflict is created whose name is the name of the attribute.
The following example shows a set of conflicts.
<Object> <Attribute name='accounts'> <List> <Object name='SimRes1'> <Attribute name='conflicts'> <List> <Object name='attr1'> <Attribute name='local' value='Safari Attr1'/> <Attribute name='original' value='Orig Attr1'/> <Attribute name='remote' value='Firefox Attr1'/> </Object> <Object name='idmManager'> <Attribute name='local' value='Mr. Safari'/> <Attribute name='original' value='Mr. Orig'/> <Attribute name='remote' value='Mr. Firefox'/> </Object> <Object name='email'> <Attribute name='local' value='safari_email'/> <Attribute name='original' value='orig_email'/> <Attribute name='remote' value='firefox_email'/> </Object> </List> </Attribute> </Object> <Object name='Lighthouse'> <Attribute name='conflicts'> <List> <Object name='idmManager'> <Attribute name='local' value='Mr. Safari'/> <Attribute name='original' value='Mr. Orig'/> <Attribute name='remote' value='Mr. Firefox'/> </Object> <Object name='email'> <Attribute name='local' value='safari_email'/> <Attribute name='original' value='orig_email'/> <Attribute name='remote' value='firefox_email'/> </Object> <Object> <Attribute name='roleInfos'> <List> <Object name='IT Role1'> <Attribute name='local'> <GenericAttribute> <Object> <Attribute name='attribute'> <Object name='IT Role1'> <Attribute name='assignedBy'> <List> <String>Business Role 2</String> </List> </Attribute> <Attribute name='assignmentType' value='required'/> <Attribute name='state' value='assigned'/> <Attribute name='type' value='ITRole'/> </Object> </Attribute> </Object> </GenericAttribute> </Attribute> <Attribute name='original'/> <Attribute name='remote'> <GenericAttribute> <Object> <Attribute name='attribute'> <Object name='IT Role1'> <Attribute name='assignedBy'> <List> <String>BusinessRole1</String> </List> </Attribute> <Attribute name='assignmentType' value='required'/> <Attribute name='state' value='assigned'/> <Attribute name='type' value='ITRole'/> </Object> </Attribute> </Object> </GenericAttribute> </Attribute> </Object> </List> </Attribute> </Object> </List> </Attribute> </Object> </List> </Attribute> </Object>
In this example, conflicts have been detected in two accounts: SimRes1 and Lighthouse. The attributes on SimRes1 for which conflicts have been detected are attr1, idmManager, and email. For Lighthouse, the attributes in conflict are idmManager, email, and roleInfos. For each conflict the original, remote, and local changed values are listed. The original value is the value of the attribute at the time the view was checked out. The remote value is the value of the attribute present in the repository (or on the resource) at the time reprovisioning started. The local value is the changed value of the attribute made by the currently executing task.
The idmManager attribute had a value of Mr. Orig when the currently running task checked out the User view (the original attribute value). The current task changed that value to Mr. Safari (the local attribute value). However, in the interim, one or more other changes have been committed which changed the attribute value to Mr. Firefox (the remote attribute value). This constitutes a conflict because the remote changes (Mr. Safari) would be lost if the local changes were committed.