Example usage for android.content ContentValues putNull

List of usage examples for android.content ContentValues putNull

Introduction

In this page you can find the example usage for android.content ContentValues putNull.

Prototype

public void putNull(String key) 

Source Link

Document

Adds a null value to the set.

Usage

From source file:org.opendatakit.services.database.utilities.ODKDatabaseImplUtils.java

/**
 * Delete any prior server conflict row.
 * Examine database and incoming server values to determine how to apply the
 * server values to the database. This might delete the existing row, update
 * it, or create a conflict row.//  w ww  .j  a  v a 2  s .c  om
 *
 * @param db
 * @param tableId
 * @param orderedColumns
 * @param serverValues  field values for this row coming from server.
 *                      All fields must have values. The SyncState field
 *                      (a local field that does not come from the server)
 *                      should be "changed" or "deleted"
 * @param rowId
 * @param activeUser
 * @param rolesList  passed in to determine if the current user is a privileged user
 * @param locale
 */
public void privilegedPerhapsPlaceRowIntoConflictWithId(OdkConnectionInterface db, String tableId,
        OrderedColumns orderedColumns, ContentValues serverValues, String rowId, String activeUser,
        String rolesList, String locale) {

    AccessContext accessContext = getAccessContext(db, tableId, activeUser, rolesList);

    boolean dbWithinTransaction = db.inTransaction();
    try {
        if (!dbWithinTransaction) {
            db.beginTransactionNonExclusive();
        }

        // The rolesList of the activeUser does not impact the execution of this portion of
        // the action.

        // PLAN:
        // (1) delete any existing server-conflict row.
        // (2) if the local row was synced or synced pending changes, then either delete
        //     the row from the device (if server deleted row) or accept the
        //     server changes and place the row into synced_pending_changes status.
        // (3) if the local row was in the new_row state, move it into changed (prior
        //     to creating a conflict pair in step 5)
        // (4) if the local row was in conflict, restore it to its pre-conflict state
        //     (either deleted or changed).
        // (5) move the local row into conflict and insert the server row, placing it
        //     into conflict.
        // (6) enforce permissions on the change. This may immediately resolve conflict
        //     by taking the server changes or may overwrite the local row's permissions
        //     column values with those from the server.
        // (7) optimize the conflict -- perhaps immediately resolving it based upon
        //     whether the user actually has the privileges to do anything other than
        //     taking the server changes or if the changes only update the tracking
        //     and (perhaps) the metadata fields.

        // Do it...

        // (1) delete any existing server conflict row
        this.deleteServerConflictRowWithId(db, tableId, rowId);
        // fetch the current local (possibly-in-conflict) row
        BaseTable baseTable = this.privilegedGetRowsWithId(db, tableId, rowId, activeUser);
        // throws will abort the transaction, rolling back these changes
        if (baseTable.getNumberOfRows() == 0) {
            throw new IllegalArgumentException("no matching row found for server conflict");
        } else if (baseTable.getNumberOfRows() != 1) {
            throw new IllegalArgumentException("row has checkpoints or database is corrupt");
        }

        Row localRow = baseTable.getRowAtIndex(0);

        if (localRow.getDataByKey(DataTableColumns.SAVEPOINT_TYPE) == null) {
            throw new IllegalArgumentException("row has checkpoints");
        }

        boolean isServerRowDeleted = serverValues.getAsString(DataTableColumns.SYNC_STATE)
                .equals(SyncState.deleted.name());

        SyncState state;
        {
            String strSyncState = localRow.getDataByKey(DataTableColumns.SYNC_STATE);
            state = SyncState.valueOf(strSyncState);
        }
        SyncState initialLocalRowState = state;

        if (state == SyncState.synced || state == SyncState.synced_pending_files) {
            // (2) if the local row was synced or synced pending changes, then either delete
            //     the row from the device (if server deleted row) or accept the
            //     server changes and place the row into synced_pending_changes status.

            // the server's change should be applied locally.
            if (isServerRowDeleted) {
                this.privilegedDeleteRowWithId(db, tableId, rowId, activeUser);
            } else {
                // Local row needs to be updated with server values.
                //
                // detect and handle file attachment column changes
                if (state == SyncState.synced) {
                    // determine whether there are any changes in the columns that hold file attachments.
                    // if there are, then we need to transition into synced_pending_files. Otherwise, we
                    // can remain in the synced state.

                    for (ColumnDefinition cd : orderedColumns.getColumnDefinitions()) {
                        // todo: does not handle array containing (types containing) rowpath elements
                        if (cd.isUnitOfRetention()
                                && cd.getType().getDataType().equals(ElementDataType.rowpath)) {
                            String uriFragment = serverValues.getAsString(cd.getElementKey());
                            String localUriFragment = localRow.getDataByKey(cd.getElementKey());
                            if (uriFragment != null) {
                                if (localUriFragment == null || !localUriFragment.equals(uriFragment)) {
                                    state = SyncState.synced_pending_files;
                                    WebLogger.getLogger(db.getAppName()).i(t,
                                            "privilegedPerhapsPlaceRowIntoConflictWithId: revising from synced to "
                                                    + "synced_pending_files");
                                    break;
                                }
                            }
                        }
                    }
                }

                // update the row from the changes on the server
                serverValues.put(DataTableColumns.SYNC_STATE, state.name());
                serverValues.putNull(DataTableColumns.CONFLICT_TYPE);
                this.privilegedUpdateRowWithId(db, tableId, orderedColumns, serverValues, rowId, activeUser,
                        locale, false);
            }

            // In either case (delete or sync outcome), be sure to commit our changes!!!!
            if (!dbWithinTransaction) {
                db.setTransactionSuccessful();
            }
            return;

        } else if (state == SyncState.new_row) {
            // (3) if the local row was in the new_row state, move it into changed (prior
            //     to creating a conflict pair in step 5)

            // update the row with all of the local columns as-is, except the sync state.
            ContentValues values = new ContentValues();
            for (int i = 0; i < baseTable.getWidth(); ++i) {
                String colName = baseTable.getElementKey(i);
                if (DataTableColumns.EFFECTIVE_ACCESS.equals(colName)) {
                    continue;
                }
                if (localRow.getDataByIndex(i) == null) {
                    values.putNull(colName);
                } else {
                    values.put(colName, localRow.getDataByIndex(i));
                }
            }
            // move this into the changed state...
            state = SyncState.changed;
            values.put(DataTableColumns.SYNC_STATE, state.name());

            this.privilegedUpdateRowWithId(db, tableId, orderedColumns, values, rowId, accessContext.activeUser,
                    locale, false);

        } else if (state == SyncState.in_conflict) {
            // (4) if the local row was in conflict, restore it to its pre-conflict state
            //     (either deleted or changed).

            // we need to remove the in_conflict records that refer to the
            // prior state of the server
            String localRowConflictTypeBeforeSyncStr = localRow.getDataByKey(DataTableColumns.CONFLICT_TYPE);
            if (localRowConflictTypeBeforeSyncStr == null) {
                // this row is in conflict. It MUST have a non-null conflict type.
                throw new IllegalStateException("conflict type is null on an in-conflict row");
            }

            int localRowConflictTypeBeforeSync = Integer.parseInt(localRowConflictTypeBeforeSyncStr);
            if (localRowConflictTypeBeforeSync == ConflictType.SERVER_DELETED_OLD_VALUES
                    || localRowConflictTypeBeforeSync == ConflictType.SERVER_UPDATED_UPDATED_VALUES) {
                // should be impossible
                throw new IllegalStateException("only the local conflict record should remain");
            }

            // move the local conflict back into the normal non-conflict (null) state
            // set the sync state to "changed" temporarily (otherwise we can't update)

            state = ((localRowConflictTypeBeforeSync == ConflictType.LOCAL_DELETED_OLD_VALUES)
                    ? SyncState.deleted
                    : SyncState.changed);

            this.restoreRowFromConflict(db, tableId, rowId, state, localRowConflictTypeBeforeSync);
        }
        // and drop through if SyncState is changed or deleted

        // (5) move the local row into conflict and insert the server row, placing it
        //     into conflict.
        int localRowConflictType = (state == SyncState.deleted) ? ConflictType.LOCAL_DELETED_OLD_VALUES
                : ConflictType.LOCAL_UPDATED_UPDATED_VALUES;

        this.placeRowIntoConflict(db, tableId, rowId, localRowConflictType);

        serverValues.put(DataTableColumns.SYNC_STATE, SyncState.in_conflict.name());
        serverValues.put(DataTableColumns.CONFLICT_TYPE,
                (isServerRowDeleted ? ConflictType.SERVER_DELETED_OLD_VALUES
                        : ConflictType.SERVER_UPDATED_UPDATED_VALUES));
        this.privilegedInsertRowWithId(db, tableId, orderedColumns, serverValues, rowId, activeUser, locale,
                false);

        // To get here, the original local row was in some state other than synced or
        // synced_pending_files. Therefore, any non-empty rowpath fields should drive
        // the row into the synced_pending_files state if we resolve the row early.

        // (6) enforce permissions on the change. This may immediately resolve conflict
        //     by taking the server changes or may overwrite the local row's permissions
        //     column values with those from the server.
        if (enforcePermissionsAndOptimizeConflictProcessing(db, tableId, orderedColumns, rowId,
                initialLocalRowState, accessContext, locale)) {
            // and...
            // (7) optimize the conflict -- perhaps immediately resolving it based upon
            //     whether the user actually has the privileges to do anything other than
            //     taking the server changes or if the changes only update the tracking
            //     and (perhaps) the metadata fields.
            optimizeConflictProcessing(db, tableId, orderedColumns, rowId, initialLocalRowState, accessContext,
                    locale);
        }

        if (!dbWithinTransaction) {
            db.setTransactionSuccessful();
        }
    } finally {
        if (!dbWithinTransaction) {
            db.endTransaction();
        }
    }
}