List of usage examples for android.content ContentValues putNull
public void putNull(String key)
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(); } } }