List of usage examples for android.content ContentValues containsKey
public boolean containsKey(String key)
From source file:com.csipsimple.db.DBProvider.java
@Override public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { SQLiteDatabase db = mOpenHelper.getWritableDatabase(); int count;// ww w .j a v a 2s . c om String finalWhere; int matched = URI_MATCHER.match(uri); List<String> possibles = getPossibleFieldsForType(matched); checkSelection(possibles, where); switch (matched) { case ACCOUNTS: count = db.update(SipProfile.ACCOUNTS_TABLE_NAME, values, where, whereArgs); break; case ACCOUNTS_ID: finalWhere = DatabaseUtilsCompat .concatenateWhere(SipProfile.FIELD_ID + " = " + ContentUris.parseId(uri), where); count = db.update(SipProfile.ACCOUNTS_TABLE_NAME, values, finalWhere, whereArgs); break; case CALLLOGS: count = db.update(SipManager.CALLLOGS_TABLE_NAME, values, where, whereArgs); break; case CALLLOGS_ID: finalWhere = DatabaseUtilsCompat.concatenateWhere(CallLog.Calls._ID + " = " + ContentUris.parseId(uri), where); count = db.update(SipManager.CALLLOGS_TABLE_NAME, values, finalWhere, whereArgs); break; case FILTERS: count = db.update(SipManager.FILTERS_TABLE_NAME, values, where, whereArgs); break; case FILTERS_ID: finalWhere = DatabaseUtilsCompat.concatenateWhere(Filter._ID + " = " + ContentUris.parseId(uri), where); count = db.update(SipManager.FILTERS_TABLE_NAME, values, finalWhere, whereArgs); break; case MESSAGES: count = db.update(SipMessage.MESSAGES_TABLE_NAME, values, where, whereArgs); break; case MESSAGES_ID: finalWhere = DatabaseUtilsCompat .concatenateWhere(SipMessage.FIELD_ID + " = " + ContentUris.parseId(uri), where); count = db.update(SipMessage.MESSAGES_TABLE_NAME, values, where, whereArgs); break; case ACCOUNTS_STATUS_ID: long id = ContentUris.parseId(uri); synchronized (profilesStatus) { SipProfileState ps = new SipProfileState(); if (profilesStatus.containsKey(id)) { ContentValues currentValues = profilesStatus.get(id); ps.createFromContentValue(currentValues); } ps.createFromContentValue(values); ContentValues cv = ps.getAsContentValue(); cv.put(SipProfileState.ACCOUNT_ID, id); profilesStatus.put(id, cv); Log.d(THIS_FILE, "Updated " + cv); } count = 1; break; default: throw new IllegalArgumentException(UNKNOWN_URI_LOG + uri); } getContext().getContentResolver().notifyChange(uri, null); long rowId = -1; if (matched == ACCOUNTS_ID || matched == ACCOUNTS_STATUS_ID) { rowId = ContentUris.parseId(uri); } if (rowId >= 0) { if (matched == ACCOUNTS_ID) { // Don't broadcast if we only changed wizard or only changed priority boolean doBroadcast = true; if (values.size() == 1) { if (values.containsKey(SipProfile.FIELD_WIZARD)) { doBroadcast = false; } else if (values.containsKey(SipProfile.FIELD_PRIORITY)) { doBroadcast = false; } } if (doBroadcast) { broadcastAccountChange(rowId); } } else if (matched == ACCOUNTS_STATUS_ID) { broadcastRegistrationChange(rowId); } } if (matched == FILTERS || matched == FILTERS_ID) { Filter.resetCache(); } return count; }
From source file:com.android.exchange.SyncManager.java
/** * See if we need to change the syncInterval for any of our PIM mailboxes based on changes * to settings in the AccountManager (sync settings). * This code is called 1) when SyncManager starts, and 2) when SyncManager is running and there * are changes made (this is detected via a SyncStatusObserver) *//*from www . j a v a 2 s .com*/ private void updatePIMSyncSettings(Account providerAccount, int mailboxType, String authority) { ContentValues cv = new ContentValues(); long mailboxId = Mailbox.findMailboxOfType(this, providerAccount.mId, mailboxType); // Presumably there is one, but if not, it's ok. Just move on... if (mailboxId != Mailbox.NO_MAILBOX) { // Create an AccountManager style Account android.accounts.Account acct = new android.accounts.Account(providerAccount.mEmailAddress, Email.EXCHANGE_ACCOUNT_MANAGER_TYPE); // Get the mailbox; this happens rarely so it's ok to get it all Mailbox mailbox = Mailbox.restoreMailboxWithId(this, mailboxId); if (mailbox == null) return; int syncInterval = mailbox.mSyncInterval; // If we're syncable, look further... if (ContentResolver.getIsSyncable(acct, authority) > 0) { // If we're supposed to sync automatically (push), set to push if it's not if (ContentResolver.getSyncAutomatically(acct, authority)) { if (syncInterval == Mailbox.CHECK_INTERVAL_NEVER || syncInterval > 0) { log("Sync for " + mailbox.mDisplayName + " in " + acct.name + ": push"); cv.put(MailboxColumns.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_PUSH); } // If we're NOT supposed to push, and we're not set up that way, change it } else if (syncInterval != Mailbox.CHECK_INTERVAL_NEVER) { log("Sync for " + mailbox.mDisplayName + " in " + acct.name + ": manual"); cv.put(MailboxColumns.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_NEVER); } // If not, set it to never check } else if (syncInterval != Mailbox.CHECK_INTERVAL_NEVER) { log("Sync for " + mailbox.mDisplayName + " in " + acct.name + ": manual"); cv.put(MailboxColumns.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_NEVER); } // If we've made a change, update the Mailbox, and kick if (cv.containsKey(MailboxColumns.SYNC_INTERVAL)) { mResolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId), cv, null, null); kick("sync settings change"); } } }
From source file:com.zns.comicdroid.activity.Edit.java
private void UpdateComics() { final ContentValues values = new ContentValues(); final DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(this); String title = mEtTitle.getText().toString().trim(); if (title.toLowerCase(Locale.ENGLISH).startsWith("the ")) { title = title.substring(4) + ", The"; }//from www .j a va 2s .co m if (mComics != null && mComics.size() > 1) { if (!isEmpty(mEtTitle)) values.put("Title", title); if (!isEmpty(mEtSubtitle)) values.put("SubTitle", mEtSubtitle.getText().toString()); if (!isEmpty(mEtAuthor)) values.put("Author", mEtAuthor.getText().toString()); if (!isEmpty(mEtIllustrator)) values.put("Illustrator", mEtIllustrator.getText().toString()); if (!isEmpty(mEtPublisher)) values.put("Publisher", mEtPublisher.getText().toString()); if (mSpGroup.getSelectedItemPosition() > 0) { Group g = (Group) mSpGroup.getSelectedItem(); values.put("GroupId", g.getId()); } } else { //Strings values.put("Title", title); values.put("SubTitle", mEtSubtitle.getText().toString()); values.put("Author", mEtAuthor.getText().toString()); values.put("Illustrator", mEtIllustrator.getText().toString()); values.put("Publisher", mEtPublisher.getText().toString()); values.put("Issues", mEtIssues.getText().toString()); //Integers if (!isEmpty(mEtIssue)) { if (isValidInt(mEtIssue.getText().toString())) { values.put("Issue", Integer.parseInt(mEtIssue.getText().toString())); } else { Toast.makeText(this, R.string.edit_issueerror, Toast.LENGTH_LONG).show(); return; } } else { values.putNull("Issue"); } if (!isEmpty(mEtPageCount)) { if (isValidInt(mEtPageCount.getText().toString())) { values.put("PageCount", Integer.parseInt(mEtPageCount.getText().toString())); } else { Toast.makeText(this, R.string.edit_pagecounterror, Toast.LENGTH_LONG).show(); return; } } else { values.putNull("PageCount"); } //Dates try { if (!isEmpty(mEtPublished)) { values.put("PublishDate", getDBHelper().GetDateStamp(mEtPublished.getText().toString(), dateFormat)); } else { values.putNull("PublishDate"); } if (!isEmpty(mEtAdded)) { values.put("AddedDate", getDBHelper().GetDateStamp(mEtAdded.getText().toString(), dateFormat)); } else { values.putNull("AddedDate"); } } catch (ParseException e) { Toast.makeText(this, getString(R.string.edit_dateerror) + " " + dateFormat.format(new Date()), Toast.LENGTH_LONG).show(); return; } //Image if (mNewImage != null) { values.put("ImageUrl", ""); values.put("Image", new File(mNewImage).getName()); } //Group if (mSpGroup.getSelectedItemPosition() > 0) { Group g = (Group) mSpGroup.getSelectedItem(); values.put("GroupId", g.getId()); } else { values.putNull("GroupId"); } } if (mComics != null) { //UPDATE StringBuilder sbWhere = new StringBuilder("_id IN ("); String[] ids = new String[mComics.size()]; int i = 0; for (Comic c : mComics) { sbWhere.append("?,"); ids[i] = Integer.toString(c.getId()); i++; } sbWhere.setLength(sbWhere.length() - 1); sbWhere.append(")"); getDBHelper().update("tblBooks", values, sbWhere.toString(), ids); } else { //INSERT if (!values.containsKey("AddedDate") || values.get("AddedDate") == null) { values.remove("AddedDate"); values.put("AddedDate", (int) (System.currentTimeMillis() / 1000L)); } long id = getDBHelper().insert("tblBooks", values); Comic comic = getDBHelper().getComic((int) id); if (comic != null) { mComics = new ArrayList<Comic>(); mComics.add(comic); } } //Backup BackupManager m = new BackupManager(this); m.dataChanged(); setResult(RESULT_OK); Toast.makeText(this, getResources().getString(R.string.edit_done), Toast.LENGTH_LONG).show(); }
From source file:org.opendatakit.common.android.provider.impl.FormsProviderImpl.java
@Override public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { List<String> segments = uri.getPathSegments(); if (segments.size() < 1 || segments.size() > 2) { throw new IllegalArgumentException("Unknown URI (incorrect number of segments!) " + uri); }// w ww .j a va 2 s . co m String appName = segments.get(0); ODKFileUtils.verifyExternalStorageAvailability(); ODKFileUtils.assertDirectoryStructure(appName); WebLogger log = WebLogger.getLogger(appName); String uriFormId = ((segments.size() == 2) ? segments.get(1) : null); boolean isNumericId = StringUtils.isNumeric(uriFormId); // Modify the where clause to account for the presence of // a form id. Accept either: // (1) numeric _ID value // (2) string FORM_ID value. String whereId; String[] whereIdArgs; if (uriFormId == null) { whereId = where; whereIdArgs = whereArgs; } else { if (TextUtils.isEmpty(where)) { whereId = (isNumericId ? FormsColumns._ID : FormsColumns.FORM_ID) + "=?"; whereIdArgs = new String[1]; whereIdArgs[0] = uriFormId; } else { whereId = (isNumericId ? FormsColumns._ID : FormsColumns.FORM_ID) + "=? AND (" + where + ")"; whereIdArgs = new String[whereArgs.length + 1]; whereIdArgs[0] = uriFormId; for (int i = 0; i < whereArgs.length; ++i) { whereIdArgs[i + 1] = whereArgs[i]; } } } /* * First, find out what records match this query, and if they refer to two * or more (formId,formVersion) tuples, then be sure to remove all * FORM_MEDIA_PATH references. Otherwise, if they are all for the same * tuple, and the update specifies a FORM_MEDIA_PATH, move all the * non-matching directories elsewhere. */ Integer idValue = null; String tableIdValue = null; String formIdValue = null; HashMap<File, DirType> mediaDirs = new HashMap<File, DirType>(); boolean multiset = false; Cursor c = null; try { c = this.query(uri, null, whereId, whereIdArgs, null); if (c == null) { throw new SQLException( "FAILED Update of " + uri + " -- query for existing row did not return a cursor"); } if (c.getCount() >= 1) { FormIdVersion ref = null; c.moveToPosition(-1); while (c.moveToNext()) { idValue = ODKDatabaseUtils.get().getIndexAsType(c, Integer.class, c.getColumnIndex(FormsColumns._ID)); tableIdValue = ODKDatabaseUtils.get().getIndexAsString(c, c.getColumnIndex(FormsColumns.TABLE_ID)); formIdValue = ODKDatabaseUtils.get().getIndexAsString(c, c.getColumnIndex(FormsColumns.FORM_ID)); String tableId = ODKDatabaseUtils.get().getIndexAsString(c, c.getColumnIndex(FormsColumns.TABLE_ID)); String formId = ODKDatabaseUtils.get().getIndexAsString(c, c.getColumnIndex(FormsColumns.FORM_ID)); String formVersion = ODKDatabaseUtils.get().getIndexAsString(c, c.getColumnIndex(FormsColumns.FORM_VERSION)); FormIdVersion cur = new FormIdVersion(tableId, formId, formVersion); int appRelativeMediaPathIdx = c.getColumnIndex(FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH); String mediaPath = ODKDatabaseUtils.get().getIndexAsString(c, appRelativeMediaPathIdx); if (mediaPath != null) { mediaDirs.put(ODKFileUtils.asAppFile(appName, mediaPath), (tableIdValue == null) ? DirType.FRAMEWORK : DirType.FORMS); } if (ref != null && !ref.equals(cur)) { multiset = true; break; } else { ref = cur; } } } } catch (Exception e) { log.w(t, "FAILED Update of " + uri + " -- query for existing row failed: " + e.toString()); if (e instanceof SQLException) { throw (SQLException) e; } else { throw new SQLException( "FAILED Update of " + uri + " -- query for existing row failed: " + e.toString()); } } finally { if (c != null) { c.close(); } } if (multiset) { // don't let users manually update media path // we are referring to two or more (formId,formVersion) tuples. if (values.containsKey(FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH)) { values.remove(FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH); } } else if (values.containsKey(FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH)) { // we are not a multiset and we are setting the media path // try to move all the existing non-matching media paths to // somewhere else... File mediaPath = ODKFileUtils.asAppFile(appName, values.getAsString(FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH)); for (HashMap.Entry<File, DirType> entry : mediaDirs.entrySet()) { File altPath = entry.getKey(); if (!altPath.equals(mediaPath)) { try { moveDirectory(appName, entry.getValue(), altPath); } catch (IOException e) { e.printStackTrace(); log.e(t, "Attempt to move " + altPath.getAbsolutePath() + " failed: " + e.toString()); } } } // OK. we have moved the existing form definitions elsewhere. We can // proceed with update... } // ensure that all values are correct and ignore some user-supplied // values... patchUpValues(appName, values); // Make sure that the necessary fields are all set if (values.containsKey(FormsColumns.DATE) == true) { Date today = new Date(); String ts = new SimpleDateFormat(getContext().getString(R.string.added_on_date_at_time), Locale.getDefault()).format(today); values.put(FormsColumns.DISPLAY_SUBTEXT, ts); } SQLiteDatabase db = null; int count; try { // OK Finally, now do the update... db = DatabaseFactory.get().getDatabase(getContext(), appName); db.beginTransaction(); count = db.update(DatabaseConstants.FORMS_TABLE_NAME, values, whereId, whereIdArgs); db.setTransactionSuccessful(); } catch (Exception e) { e.printStackTrace(); log.w(t, "Unable to perform update " + uri); return 0; } finally { if (db != null) { db.endTransaction(); db.close(); } } if (count == 1) { Uri formUri = Uri.withAppendedPath( Uri.withAppendedPath(Uri.parse("content://" + getFormsAuthority()), appName), formIdValue); getContext().getContentResolver().notifyChange(formUri, null); Uri idUri = Uri.withAppendedPath( Uri.withAppendedPath(Uri.parse("content://" + getFormsAuthority()), appName), Long.toString(idValue)); getContext().getContentResolver().notifyChange(idUri, null); } else { getContext().getContentResolver().notifyChange(uri, null); } return count; }
From source file:org.opendatakit.services.database.utlities.ODKDatabaseImplUtils.java
/** * Inserts a checkpoint row for the given rowId in the tableId. Checkpoint * rows are created by ODK Survey to hold intermediate values during the * filling-in of the form. They act as restore points in the Survey, should * the application die.// ww w . j a v a 2 s. c o m * * @param db * @param tableId * @param orderedColumns * @param cvValues * @param rowId * @param activeUser * @param rolesList * @param locale */ public void insertCheckpointRowWithId(OdkConnectionInterface db, String tableId, OrderedColumns orderedColumns, ContentValues cvValues, String rowId, String activeUser, String rolesList, String locale) throws ActionNotAuthorizedException { if (cvValues.size() <= 0) { throw new IllegalArgumentException(t + ": No values to add into table for checkpoint" + tableId); } // these are all managed in the database layer... // the user should NOT set them... if (cvValues.containsKey(DataTableColumns.SAVEPOINT_TIMESTAMP)) { throw new IllegalArgumentException( t + ": No user supplied savepoint timestamp can be included for a checkpoint"); } if (cvValues.containsKey(DataTableColumns.SAVEPOINT_TYPE)) { throw new IllegalArgumentException( t + ": No user supplied savepoint type can be included for a checkpoint"); } if (cvValues.containsKey(DataTableColumns.ROW_ETAG)) { throw new IllegalArgumentException(t + ": No user supplied row ETag can be included for a checkpoint"); } if (cvValues.containsKey(DataTableColumns.SYNC_STATE)) { throw new IllegalArgumentException( t + ": No user supplied sync state can be included for a checkpoint"); } if (cvValues.containsKey(DataTableColumns.CONFLICT_TYPE)) { throw new IllegalArgumentException( t + ": No user supplied conflict type can be included for a checkpoint"); } if (cvValues.containsKey(DataTableColumns.FILTER_VALUE)) { throw new IllegalArgumentException( t + ": No user supplied filter value can be included for a checkpoint"); } if (cvValues.containsKey(DataTableColumns.FILTER_TYPE)) { throw new IllegalArgumentException( t + ": No user supplied filter type can be included for a checkpoint"); } // If a rowId is specified, a cursor will be needed to // get the current row to create a checkpoint with the relevant data Cursor c = null; try { // Allow the user to pass in no rowId if this is the first // checkpoint row that the user is adding if (rowId == null) { // TODO: is this even valid any more? I think we disallow this in the AIDL flow. String rowIdToUse = LocalizationUtils.genUUID(); HashMap<String, Object> currValues = new HashMap<String, Object>(); for (String key : cvValues.keySet()) { currValues.put(key, cvValues.get(key)); } currValues.put(DataTableColumns._ID, rowIdToUse); currValues.put(DataTableColumns.SYNC_STATE, SyncState.new_row.name()); insertCheckpointIntoExistingTable(db, tableId, orderedColumns, currValues, activeUser, rolesList, locale, true, null, null); return; } StringBuilder b = new StringBuilder(); b.append(K_DATATABLE_ID_EQUALS_PARAM).append(S_AND).append(DataTableColumns.SAVEPOINT_TIMESTAMP) .append(" IN (SELECT MAX(").append(DataTableColumns.SAVEPOINT_TIMESTAMP).append(") FROM ") .append(tableId).append(K_WHERE).append(K_DATATABLE_ID_EQUALS_PARAM).append(")"); c = db.query(tableId, null, b.toString(), new Object[] { rowId, rowId }, null, null, null, null); c.moveToFirst(); if (c.getCount() > 1) { throw new IllegalStateException(t + ": More than one checkpoint at a timestamp"); } // Inserting a checkpoint for the first time if (c.getCount() <= 0) { HashMap<String, Object> currValues = new HashMap<String, Object>(); for (String key : cvValues.keySet()) { currValues.put(key, cvValues.get(key)); } currValues.put(DataTableColumns._ID, rowId); currValues.put(DataTableColumns.SYNC_STATE, SyncState.new_row.name()); insertCheckpointIntoExistingTable(db, tableId, orderedColumns, currValues, activeUser, rolesList, locale, true, null, null); return; } else { // Make sure that the conflict_type of any existing row // is null, otherwise throw an exception int conflictIndex = c.getColumnIndex(DataTableColumns.CONFLICT_TYPE); if (!c.isNull(conflictIndex)) { throw new IllegalStateException( t + ": A checkpoint cannot be added for a row that is in conflict"); } HashMap<String, Object> currValues = new HashMap<String, Object>(); for (String key : cvValues.keySet()) { currValues.put(key, cvValues.get(key)); } // This is unnecessary // We should only have one row at this point //c.moveToFirst(); String priorFilterType = null; String priorFilterValue = null; // Get the number of columns to iterate over and add // those values to the content values for (int i = 0; i < c.getColumnCount(); i++) { String name = c.getColumnName(i); if (currValues.containsKey(name)) { continue; } // omitting savepoint timestamp will generate a new timestamp. if (name.equals(DataTableColumns.SAVEPOINT_TIMESTAMP)) { continue; } // set savepoint type to null to mark this as a checkpoint if (name.equals(DataTableColumns.SAVEPOINT_TYPE)) { currValues.put(name, null); continue; } // sync state (a non-null field) should either remain 'new_row' // or be set to 'changed' for all other existing values. if (name.equals(DataTableColumns.SYNC_STATE)) { String priorState = c.getString(i); if (priorState.equals(SyncState.new_row.name())) { currValues.put(name, SyncState.new_row.name()); } else { currValues.put(name, SyncState.changed.name()); } continue; } if (c.isNull(i)) { currValues.put(name, null); continue; } // otherwise, just copy the values over... Class<?> theClass = CursorUtils.getIndexDataType(c, i); Object object = CursorUtils.getIndexAsType(c, theClass, i); insertValueIntoContentValues(currValues, theClass, name, object); if (name.equals(DataTableColumns.FILTER_TYPE)) { priorFilterType = c.getString(i); } if (name.equals(DataTableColumns.FILTER_VALUE)) { priorFilterValue = c.getString(i); } } insertCheckpointIntoExistingTable(db, tableId, orderedColumns, currValues, activeUser, rolesList, locale, false, priorFilterType, priorFilterValue); } } finally { if (c != null && !c.isClosed()) { c.close(); } } }
From source file:org.opendatakit.services.database.utilities.ODKDatabaseImplUtils.java
/** * Inserts a checkpoint row for the given rowId in the tableId. Checkpoint * rows are created by ODK Survey to hold intermediate values during the * filling-in of the form. They act as restore points in the Survey, should * the application die./* w w w .j av a 2 s. co m*/ * * @param db * @param tableId * @param orderedColumns * @param cvValues * @param rowId * @param activeUser * @param rolesList * @param locale */ public void insertCheckpointRowWithId(OdkConnectionInterface db, String tableId, OrderedColumns orderedColumns, ContentValues cvValues, String rowId, String activeUser, String rolesList, String locale) throws ActionNotAuthorizedException { if (cvValues.size() <= 0) { throw new IllegalArgumentException(t + ": No values to add into table for checkpoint" + tableId); } // these are all managed in the database layer... // the user should NOT set them... if (cvValues.containsKey(DataTableColumns.SAVEPOINT_TIMESTAMP)) { throw new IllegalArgumentException( t + ": No user supplied savepoint timestamp can be included for a checkpoint"); } if (cvValues.containsKey(DataTableColumns.SAVEPOINT_TYPE)) { throw new IllegalArgumentException( t + ": No user supplied savepoint type can be included for a checkpoint"); } if (cvValues.containsKey(DataTableColumns.ROW_ETAG)) { throw new IllegalArgumentException(t + ": No user supplied row ETag can be included for a checkpoint"); } if (cvValues.containsKey(DataTableColumns.SYNC_STATE)) { throw new IllegalArgumentException( t + ": No user supplied sync state can be included for a checkpoint"); } if (cvValues.containsKey(DataTableColumns.CONFLICT_TYPE)) { throw new IllegalArgumentException( t + ": No user supplied conflict type can be included for a checkpoint"); } // If a rowId is specified, a cursor will be needed to // get the current row to create a checkpoint with the relevant data Cursor c = null; try { // Allow the user to pass in no rowId if this is the first // checkpoint row that the user is adding if (rowId == null) { // TODO: is this even valid any more? I think we disallow this in the AIDL flow. String rowIdToUse = LocalizationUtils.genUUID(); HashMap<String, Object> currValues = new HashMap<String, Object>(); for (String key : cvValues.keySet()) { currValues.put(key, cvValues.get(key)); } currValues.put(DataTableColumns._ID, rowIdToUse); currValues.put(DataTableColumns.SYNC_STATE, SyncState.new_row.name()); insertCheckpointIntoExistingTable(db, tableId, orderedColumns, currValues, activeUser, rolesList, locale, true, null, null, null, null, null); return; } StringBuilder b = new StringBuilder(); b.append(K_DATATABLE_ID_EQUALS_PARAM).append(S_AND).append(DataTableColumns.SAVEPOINT_TIMESTAMP) .append(" IN (SELECT MAX(").append(DataTableColumns.SAVEPOINT_TIMESTAMP).append(") FROM ") .append(tableId).append(K_WHERE).append(K_DATATABLE_ID_EQUALS_PARAM).append(")"); c = db.query(tableId, null, b.toString(), new Object[] { rowId, rowId }, null, null, null, null); c.moveToFirst(); if (c.getCount() > 1) { throw new IllegalStateException(t + ": More than one checkpoint at a timestamp"); } // Inserting a checkpoint for the first time if (c.getCount() <= 0) { HashMap<String, Object> currValues = new HashMap<String, Object>(); for (String key : cvValues.keySet()) { currValues.put(key, cvValues.get(key)); } currValues.put(DataTableColumns._ID, rowId); currValues.put(DataTableColumns.SYNC_STATE, SyncState.new_row.name()); insertCheckpointIntoExistingTable(db, tableId, orderedColumns, currValues, activeUser, rolesList, locale, true, null, null, null, null, null); return; } else { // Make sure that the conflict_type of any existing row // is null, otherwise throw an exception int conflictIndex = c.getColumnIndex(DataTableColumns.CONFLICT_TYPE); if (!c.isNull(conflictIndex)) { throw new IllegalStateException( t + ": A checkpoint cannot be added for a row that is in conflict"); } // these are all managed in the database layer... // the user should NOT set them... if (cvValues.containsKey(DataTableColumns.DEFAULT_ACCESS)) { throw new IllegalArgumentException( t + ": No user supplied default access can be included for a checkpoint"); } if (cvValues.containsKey(DataTableColumns.ROW_OWNER)) { throw new IllegalArgumentException( t + ": No user supplied row owner can be included for a checkpoint"); } if (cvValues.containsKey(DataTableColumns.GROUP_READ_ONLY)) { throw new IllegalArgumentException( t + ": No user supplied group read only can be included for a checkpoint"); } if (cvValues.containsKey(DataTableColumns.GROUP_MODIFY)) { throw new IllegalArgumentException( t + ": No user supplied group modify can be included for a checkpoint"); } if (cvValues.containsKey(DataTableColumns.GROUP_PRIVILEGED)) { throw new IllegalArgumentException( t + ": No user supplied group privileged can be included for a checkpoint"); } HashMap<String, Object> currValues = new HashMap<String, Object>(); for (String key : cvValues.keySet()) { currValues.put(key, cvValues.get(key)); } // This is unnecessary // We should only have one row at this point //c.moveToFirst(); String priorDefaultAccess = null; String priorOwner = null; String priorGroupReadOnly = null; String priorGroupModify = null; String priorGroupPrivileged = null; // Get the number of columns to iterate over and add // those values to the content values for (int i = 0; i < c.getColumnCount(); i++) { String name = c.getColumnName(i); if (name.equals(DataTableColumns.DEFAULT_ACCESS)) { priorDefaultAccess = c.getString(i); } if (name.equals(DataTableColumns.ROW_OWNER)) { priorOwner = c.getString(i); } if (name.equals(DataTableColumns.GROUP_READ_ONLY)) { priorGroupReadOnly = c.getString(i); } if (name.equals(DataTableColumns.GROUP_MODIFY)) { priorGroupModify = c.getString(i); } if (name.equals(DataTableColumns.GROUP_PRIVILEGED)) { priorGroupPrivileged = c.getString(i); } if (currValues.containsKey(name)) { continue; } // omitting savepoint timestamp will generate a new timestamp. if (name.equals(DataTableColumns.SAVEPOINT_TIMESTAMP)) { continue; } // set savepoint type to null to mark this as a checkpoint if (name.equals(DataTableColumns.SAVEPOINT_TYPE)) { currValues.put(name, null); continue; } // sync state (a non-null field) should either remain 'new_row' // or be set to 'changed' for all other existing values. if (name.equals(DataTableColumns.SYNC_STATE)) { String priorState = c.getString(i); if (priorState.equals(SyncState.new_row.name())) { currValues.put(name, SyncState.new_row.name()); } else { currValues.put(name, SyncState.changed.name()); } continue; } if (c.isNull(i)) { currValues.put(name, null); continue; } // otherwise, just copy the values over... Class<?> theClass = CursorUtils.getIndexDataType(c, i); Object object = CursorUtils.getIndexAsType(c, theClass, i); insertValueIntoContentValues(currValues, theClass, name, object); } insertCheckpointIntoExistingTable(db, tableId, orderedColumns, currValues, activeUser, rolesList, locale, false, priorDefaultAccess, priorOwner, priorGroupReadOnly, priorGroupModify, priorGroupPrivileged); } } finally { if (c != null && !c.isClosed()) { c.close(); } } }
From source file:org.opendatakit.services.database.utlities.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 w w . j a va 2 s. c o m * * @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); // The rolesList of the activeUser does not impact the execution of this action. boolean dbWithinTransaction = db.inTransaction(); try { if (!dbWithinTransaction) { db.beginTransactionNonExclusive(); } // 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); 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"); } String strSyncState = localRow.getDataByKey(DataTableColumns.SYNC_STATE); SyncState state = SyncState.valueOf(strSyncState); int localRowConflictTypeBeforeSync = -1; if (state == SyncState.in_conflict) { // 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"); } 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"); } } boolean isServerRowDeleted = serverValues.getAsString(DataTableColumns.SYNC_STATE) .equals(SyncState.deleted.name()); boolean executeDropThrough = false; if (isServerRowDeleted) { if (state == SyncState.synced) { // the server's change should be applied locally. this.privilegedDeleteRowWithId(db, tableId, rowId, activeUser); } else if (state == SyncState.synced_pending_files) { // sync logic may want to UPLOAD local files up to server before calling this routine. // this would prevent loss of attachments that have not yet been pushed to server. // the server's change should be applied locally. this.privilegedDeleteRowWithId(db, tableId, rowId, activeUser); } else if ((state == SyncState.deleted) || ((state == SyncState.in_conflict) && (localRowConflictTypeBeforeSync == ConflictType.LOCAL_DELETED_OLD_VALUES))) { // this occurs if // (1) a delete request was never ACKed but it was performed // on the server. // (2) if there is an unresolved conflict held locally with the // local action being to delete the record, and the prior server // state being a value change, but the newly sync'd state now // reflects a deletion by another party. // // the server's change should be applied locally. this.privilegedDeleteRowWithId(db, tableId, rowId, activeUser); } else { // need to resolve a conflict situation... executeDropThrough = true; } } else if (state == SyncState.synced || state == SyncState.synced_pending_files) { // When a prior sync ends with conflicts, we will not update the table's "lastDataETag" // and when we next sync, we will pull the same server row updates as when the // conflicts were raised (elsewhere in the table). // // Therefore, we can expect many server row updates to have already been locally // applied (for the rows were not in conflict). Detect and ignore these already- // processed changes by testing for the server and device having identical field values. // boolean isDifferent = false; for (int i = 0; i < baseTable.getWidth(); ++i) { String colName = baseTable.getElementKey(i); if (DataTableColumns.ID.equals(colName) || DataTableColumns.CONFLICT_TYPE.equals(colName) || DataTableColumns.EFFECTIVE_ACCESS.equals(colName) || DataTableColumns.SYNC_STATE.equals(colName)) { // these values are ignored during comparisons continue; } String localValue = localRow.getDataByKey(colName); String serverValue = serverValues.containsKey(colName) ? serverValues.getAsString(colName) : null; ElementDataType dt = ElementDataType.string; try { ColumnDefinition cd = orderedColumns.find(colName); dt = cd.getType().getDataType(); } catch (IllegalArgumentException e) { // ignore } if (!identicalValue(localValue, serverValue, dt)) { isDifferent = true; break; } } if (isDifferent) { // Local row needs to be updated with server values. // // detect and handle file attachment column changes and sync state // (need to change serverRow value of this) boolean hasNonNullAttachments = false; 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)) { hasNonNullAttachments = true; break; } } } } // update the row from the changes on the server ContentValues values = new ContentValues(serverValues); values.put(DataTableColumns.SYNC_STATE, (hasNonNullAttachments || (state == SyncState.synced_pending_files)) ? SyncState.synced_pending_files.name() : SyncState.synced.name()); values.putNull(DataTableColumns.CONFLICT_TYPE); this.privilegedUpdateRowWithId(db, tableId, orderedColumns, values, rowId, activeUser, locale, false); } // and don't execute the drop-through in this case. } else { executeDropThrough = true; } if (executeDropThrough) { // SyncState.deleted and server is not deleting // SyncState.new_row and record exists on server // SyncState.changed and new change (or delete) on server // SyncState.in_conflict and new change (or delete) on server // ALSO: this case can occur when our prior sync attempt pulled down changes that // placed local row(s) in conflict -- which we have since resolved -- and we are now // issuing a sync to push those changes up to the server. // // This is because we do not update our local table's "lastDataETag" until we have // sync'd and applied all changes from the server and have no local conflicts or // checkpoints on the table. Because of this, when we issue a sync after resolving // a conflict, we will get the set of server row changes that include the row(s) // that were previously in conflict. This will appear as one of: // changed | changed // changed | deleted // deleted | changed // deleted | deleted // // BUT, this time, however, the local records will have the same rowETag as the // server record, indicating that they are valid changes (or deletions) on top of // the server's current version of this same row. // // If this is the case (the rowETags match), then we should not place the row into // conflict, but should instead ignore the reported content from the server and push // the local row's change or delete up to the server in the next section. // // If not, when we reach this point in the code, the rowETag of the server row // should **not match** our local row -- indicating that the server has a change from // another source, and that we should place the row into conflict. // String localRowETag = localRow.getDataByKey(DataTableColumns.ROW_ETAG); String serverRowETag = serverValues.getAsString(DataTableColumns.ROW_ETAG); boolean isDifferentRowETag = (localRowETag == null) || !localRowETag.equals(serverRowETag); if (!isDifferentRowETag) { // ignore the server record. // This is an update we will push to the server. // todo: make sure local row is not in_conflict at this point. if (state == SyncState.in_conflict) { // don't think this is logically possible at this point, but we should // transition record to changed or deleted 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)); } } values.put(DataTableColumns.ID, rowId); values.putNull(DataTableColumns.CONFLICT_TYPE); values.put(DataTableColumns.SYNC_STATE, (localRowConflictTypeBeforeSync == ConflictType.LOCAL_DELETED_OLD_VALUES) ? SyncState.deleted.name() : SyncState.changed.name()); this.privilegedUpdateRowWithId(db, tableId, orderedColumns, values, rowId, activeUser, locale, false); } } else { // figure out what the localRow conflict type should be... int localRowConflictType; if (state == SyncState.changed) { // SyncState.changed and new change on server localRowConflictType = ConflictType.LOCAL_UPDATED_UPDATED_VALUES; } else if (state == SyncState.new_row) { // SyncState.new_row and record exists on server // The 'new_row' case occurs if an insert is never ACKed but // completes successfully on the server. localRowConflictType = ConflictType.LOCAL_UPDATED_UPDATED_VALUES; } else if (state == SyncState.deleted) { // SyncState.deleted and server is not deleting localRowConflictType = ConflictType.LOCAL_DELETED_OLD_VALUES; } else if (state == SyncState.in_conflict) { // SyncState.in_conflict and new change on server // leave the local conflict type unchanged (retrieve it and // use it). localRowConflictType = localRowConflictTypeBeforeSync; } else { throw new IllegalStateException("Unexpected state encountered"); } boolean isDifferentPrivilegedFields = false; { String[] privilegedColumns = new String[] { DataTableColumns.FILTER_TYPE, DataTableColumns.FILTER_VALUE }; for (int i = 0; i < privilegedColumns.length; ++i) { String colName = privilegedColumns[i]; String localValue = localRow.getDataByKey(colName); String serverValue = serverValues.containsKey(colName) ? serverValues.getAsString(colName) : null; ElementDataType dt = ElementDataType.string; try { ColumnDefinition cd = orderedColumns.find(colName); dt = cd.getType().getDataType(); } catch (IllegalArgumentException e) { // ignore } boolean sameValue = identicalValue(localValue, serverValue, dt); if (!sameValue) { isDifferentPrivilegedFields = true; break; } } } boolean isDifferentExcludingPrivilegedFields = false; for (int i = 0; i < baseTable.getWidth(); ++i) { String colName = baseTable.getElementKey(i); if (DataTableColumns.ID.equals(colName) || DataTableColumns.CONFLICT_TYPE.equals(colName) || DataTableColumns.EFFECTIVE_ACCESS.equals(colName) || DataTableColumns.SYNC_STATE.equals(colName) || DataTableColumns.ROW_ETAG.equals(colName) || DataTableColumns.FILTER_TYPE.equals(colName) || DataTableColumns.FILTER_VALUE.equals(colName)) { // these values are ignored during this comparison continue; } String localValue = localRow.getDataByKey(colName); String serverValue = serverValues.containsKey(colName) ? serverValues.getAsString(colName) : null; ElementDataType dt = ElementDataType.string; try { ColumnDefinition cd = orderedColumns.find(colName); dt = cd.getType().getDataType(); } catch (IllegalArgumentException e) { // ignore } if (serverValue != null && dt == ElementDataType.bool) { serverValue = Integer .toString(DataHelper.boolToInt(serverValues.getAsBoolean(colName))); } boolean sameValue = identicalValue(localValue, serverValue, dt); if (!sameValue) { isDifferentExcludingPrivilegedFields = true; break; } } boolean hasNonNullDifferingServerAttachments = false; if (isDifferentExcludingPrivilegedFields || (state != SyncState.synced)) { 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)) { hasNonNullDifferingServerAttachments = true; } } } } } ContentValues values = new ContentValues(serverValues); if (isDifferentExcludingPrivilegedFields || isDifferentPrivilegedFields || isDifferentRowETag || isServerRowDeleted) { if (isDifferentExcludingPrivilegedFields || isServerRowDeleted || (localRowConflictType == ConflictType.LOCAL_DELETED_OLD_VALUES) || (accessContext.isPrivilegedUser && isDifferentPrivilegedFields)) { 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); } else { // just apply the server RowETag and filterScope to the local row values.put(DataTableColumns.SYNC_STATE, hasNonNullDifferingServerAttachments ? SyncState.synced_pending_files.name() : SyncState.synced.name()); values.putNull(DataTableColumns.CONFLICT_TYPE); // move the local conflict back into the normal non-conflict (null) state // set the sync state to "changed" temporarily (otherwise we can't update) this.restoreRowFromConflict(db, tableId, rowId, SyncState.changed, localRowConflictTypeBeforeSync); this.privilegedUpdateRowWithId(db, tableId, orderedColumns, values, rowId, activeUser, locale, false); } } else { // data matches -- update row to adjust sync state and conflict type // if needed. SyncState destState = (hasNonNullDifferingServerAttachments || (state == SyncState.synced_pending_files)) ? SyncState.synced_pending_files : SyncState.synced; if ((state != destState) || isDifferentRowETag || (localRow.getDataByKey(DataTableColumns.CONFLICT_TYPE) != null)) { // todo: handle case where local row was in conflict // server has now matched the local row's state. i.e., // update rowEtag, clear conflictType and adjust syncState on row. values.put(DataTableColumns.SYNC_STATE, destState.name()); values.putNull(DataTableColumns.CONFLICT_TYPE); // move the local conflict back into the normal non-conflict (null) state // set the sync state to "changed" temporarily (otherwise we can't update) this.restoreRowFromConflict(db, tableId, rowId, SyncState.changed, localRowConflictTypeBeforeSync); this.privilegedUpdateRowWithId(db, tableId, orderedColumns, values, rowId, activeUser, locale, false); WebLogger.getLogger(db.getAppName()).w(t, "identical rows returned from server -- " + "SHOULDN'T THESE NOT HAPPEN?"); } } } } if (!dbWithinTransaction) { db.setTransactionSuccessful(); } } finally { if (!dbWithinTransaction) { db.endTransaction(); } } }