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:com.akop.bach.parser.PsnEuParser.java

@SuppressLint("DefaultLocale")
@Override//  ww w .j a  v a 2s .  c om
protected void parseFriends(PsnAccount account) throws ParserException, IOException {
    synchronized (PsnEuParser.class) {
        ContentResolver cr = mContext.getContentResolver();
        ContentValues cv;
        List<ContentValues> newCvs = new ArrayList<ContentValues>(100);
        final long accountId = account.getId();

        int rowsInserted = 0;
        int rowsUpdated = 0;
        int rowsDeleted = 0;

        long updated = System.currentTimeMillis();
        long started = updated;

        // Handle pending requests
        String page = getResponse(URL_FRIENDS);

        Matcher m;
        Matcher friendMatcher = PATTERN_FRIENDS_PENDING.matcher(page);

        while (friendMatcher.find()) {
            String onlineId = htmlDecode(friendMatcher.group(1));
            Cursor c = cr.query(Friends.CONTENT_URI, FRIEND_ID_PROJECTION,
                    Friends.ACCOUNT_ID + "=" + account.getId() + " AND " + Friends.ONLINE_ID + "=?",
                    new String[] { onlineId }, null);

            long friendId = -1;

            try {
                if (c != null && c.moveToFirst())
                    friendId = c.getLong(0);
            } finally {
                if (c != null)
                    c.close();
            }

            cv = new ContentValues(15);

            cv.put(Friends.DELETE_MARKER, updated);
            cv.put(Friends.ONLINE_STATUS, PSN.STATUS_PENDING);

            if (friendId < 0) {
                // New
                cv.put(Friends.ONLINE_ID, onlineId);
                cv.put(Friends.ACCOUNT_ID, accountId);
                cv.put(Friends.PROGRESS, 0);
                cv.putNull(Friends.ICON_URL);
                cv.put(Friends.LEVEL, 0);
                cv.put(Friends.TROPHIES_PLATINUM, 0);
                cv.put(Friends.TROPHIES_GOLD, 0);
                cv.put(Friends.TROPHIES_SILVER, 0);
                cv.put(Friends.TROPHIES_BRONZE, 0);
                cv.putNull(Friends.PLAYING);
                cv.put(Friends.LAST_UPDATED, 0);

                newCvs.add(cv);
            } else {
                cr.update(ContentUris.withAppendedId(Friends.CONTENT_URI, friendId), cv, null, null);

                rowsUpdated++;
            }
        }

        // Handle rest of friends
        page = getResponse(URL_FRIENDS_AJAX);
        friendMatcher = PATTERN_FRIENDS.matcher(page);

        while (friendMatcher.find()) {
            String friendData = friendMatcher.group(1);

            String onlineId;
            if (!(m = PATTERN_FRIEND_ONLINE_ID.matcher(friendData)).find())
                continue;

            onlineId = htmlDecode(m.group(1));

            int level = 0;
            if ((m = PATTERN_FRIEND_LEVEL.matcher(friendData)).find())
                level = Integer.parseInt(m.group(1));

            String iconUrl = null;
            if ((m = PATTERN_FRIEND_AVATAR.matcher(friendData)).find())
                iconUrl = getLargeAvatarIcon(resolveImageUrl(URL_FRIENDS_AJAX, m.group(1)));

            String comment = null;
            if ((m = PATTERN_FRIEND_COMMENT.matcher(friendData)).find()) {
                comment = htmlDecode(m.group(1));
                if (comment != null && comment.equals("null"))
                    comment = null;
            }

            int memberType = PSN.MEMBER_TYPE_FREE;
            if ((m = PATTERN_FRIEND_IS_PLUS.matcher(friendData)).find()
                    && m.group(1).equalsIgnoreCase("true")) {
                memberType = PSN.MEMBER_TYPE_PLUS;
            }

            int bronze = 0;
            int silver = 0;
            int gold = 0;
            int platinum = 0;

            m = PATTERN_FRIEND_TROPHY.matcher(friendData);
            while (m.find()) {
                String type = m.group(1).toLowerCase();
                if ("bronze".equals(type))
                    bronze = Integer.parseInt(m.group(2));
                else if ("silver".equals(type))
                    silver = Integer.parseInt(m.group(2));
                else if ("gold".equals(type))
                    gold = Integer.parseInt(m.group(2));
                else if ("platinum".equals(type))
                    platinum = Integer.parseInt(m.group(2));
            }

            boolean inGame = false;
            int status = PSN.STATUS_OTHER;
            if ((m = PATTERN_FRIEND_STATUS.matcher(friendData)).find()) {
                String presence = m.group(1).toLowerCase();
                if (presence.equals("offline"))
                    status = PSN.STATUS_OFFLINE;
                else if (presence.equals("online"))
                    status = PSN.STATUS_ONLINE;
                else if (presence.equals("online-ingame")) {
                    status = PSN.STATUS_ONLINE;
                    inGame = true;
                } else if (presence.equals("online-away"))
                    status = PSN.STATUS_AWAY;
                else if (presence.equals("online-ingame-away")) {
                    status = PSN.STATUS_AWAY;
                    inGame = true;
                } else if (presence.equals("pending"))
                    status = PSN.STATUS_PENDING;
            }

            String playing = null;
            if ((m = PATTERN_FRIEND_PLAYING.matcher(friendData)).find()) {
                String activity = htmlDecode(m.group(1)).trim();

                if (activity != null && activity.length() > 0) {
                    if (inGame)
                        playing = mContext.getString(R.string.playing_f, activity);
                    else
                        playing = activity;
                }
            }

            Cursor c = cr.query(Friends.CONTENT_URI, FRIEND_ID_PROJECTION,
                    Friends.ACCOUNT_ID + "=" + account.getId() + " AND " + Friends.ONLINE_ID + "=?",
                    new String[] { onlineId }, null);

            long friendId = -1;

            try {
                if (c != null && c.moveToFirst())
                    friendId = c.getLong(0);
            } finally {
                if (c != null)
                    c.close();
            }

            cv = new ContentValues(15);

            cv.put(Friends.ICON_URL, iconUrl);
            cv.put(Friends.LEVEL, level);
            cv.put(Friends.MEMBER_TYPE, memberType);
            cv.put(Friends.COMMENT, comment);
            cv.put(Friends.LEVEL, level);
            cv.put(Friends.ONLINE_STATUS, status);
            cv.put(Friends.TROPHIES_PLATINUM, platinum);
            cv.put(Friends.TROPHIES_GOLD, gold);
            cv.put(Friends.TROPHIES_SILVER, silver);
            cv.put(Friends.TROPHIES_BRONZE, bronze);
            cv.put(Friends.PLAYING, playing);
            cv.put(Friends.DELETE_MARKER, updated);

            if (friendId < 0) {
                // New
                cv.put(Friends.ONLINE_ID, onlineId);
                cv.put(Friends.ACCOUNT_ID, accountId);
                cv.put(Friends.PROGRESS, 0);
                cv.put(Friends.LAST_UPDATED, 0);

                newCvs.add(cv);
            } else {
                cr.update(ContentUris.withAppendedId(Friends.CONTENT_URI, friendId), cv, null, null);

                rowsUpdated++;
            }
        }

        // Remove friends
        rowsDeleted = cr.delete(Friends.CONTENT_URI,
                Friends.ACCOUNT_ID + "=" + accountId + " AND " + Friends.DELETE_MARKER + "!=" + updated, null);

        if (newCvs.size() > 0) {
            ContentValues[] cvs = new ContentValues[newCvs.size()];
            newCvs.toArray(cvs);

            rowsInserted = cr.bulkInsert(Friends.CONTENT_URI, cvs);
        }

        account.refresh(Preferences.get(mContext));
        account.setLastFriendUpdate(System.currentTimeMillis());
        account.save(Preferences.get(mContext));

        cr.notifyChange(Friends.CONTENT_URI, null);

        if (App.getConfig().logToConsole())
            started = displayTimeTaken("Friend page processing [I:" + rowsInserted + ";U:" + rowsUpdated + ";D:"
                    + rowsDeleted + "]", started);
    }
}

From source file:org.opendatakit.tables.utils.CollectUtil.java

/**
 * This gets a map of values for insertion into a row after returning from a
 * Collect form. It handles validating the values. Null values are passed back
 * if the value is not present or is null in the return from ODK Collect (to
 * support clearing of values)./*from w w w.  j a v  a 2 s.c o m*/
 * 
 * TODO: add support for select-multiple
 *
 * @return
 */
public static ContentValues getMapForInsertion(Context context, String appName, String tableId,
        ArrayList<ColumnDefinition> orderedDefns, FormValues formValues) {

    DataUtil du = new DataUtil(Locale.ENGLISH, TimeZone.getDefault());

    ContentValues values = new ContentValues();

    List<ColumnDefinition> geopointList = GeoColumnUtil.get().getGeopointColumnDefinitions(orderedDefns);
    List<ColumnDefinition> uriList = RowPathColumnUtil.get().getUriColumnDefinitions(orderedDefns);

    for (ColumnDefinition cd : orderedDefns) {
        ColumnDefinition cdContainingElement = cd.getParent();

        if (cdContainingElement != null) {
            if (geopointList.contains(cdContainingElement) || uriList.contains(cdContainingElement)) {
                // processed by the containing type
                continue;
            }
            // and if this is not a unit of retention, a containing element is
            // handling it.
            if (!cd.isUnitOfRetention()) {
                continue;
            }
        }
        // ok. we are directly processing this... and possibly sucking values
        // out of sub-elements...
        ElementType type = cd.getType();
        if (geopointList.contains(cd)) {
            // find its children...
            List<ColumnDefinition> children = cd.getChildren();
            ColumnDefinition[] cparray = new ColumnDefinition[4];
            if (!children.isEmpty()) {
                cparray[0] = children.get(0);
            }
            if (children.size() > 1) {
                cparray[1] = children.get(1);
            }
            if (children.size() > 2) {
                cparray[2] = children.get(2);
            }
            if (children.size() > 3) {
                cparray[3] = children.get(3);
            }
            ColumnDefinition cplat = null, cplng = null, cpalt = null, cpacc = null;
            for (ColumnDefinition scp : cparray) {
                if (scp.getElementName().equals("latitude")) {
                    cplat = scp;
                } else if (scp.getElementName().equals("longitude")) {
                    cplng = scp;
                } else if (scp.getElementName().equals("altitude")) {
                    cpalt = scp;
                } else if (scp.getElementName().equals("accuracy")) {
                    cpacc = scp;
                }
            }
            // split ODK COLLECT value into the constituent elements
            String value = formValues.formValues.get(cd.getElementKey());
            if (value == null || value.length() == 0) {
                values.putNull(cplat.getElementKey());
                values.putNull(cplng.getElementKey());
                values.putNull(cpalt.getElementKey());
                values.putNull(cpacc.getElementKey());
            } else {
                String[] parts = value.split(" ");
                if (parts.length > 0) {
                    values.put(cplat.getElementKey(), parts[0]);
                } else {
                    values.putNull(cplat.getElementKey());
                }
                if (parts.length > 1) {
                    values.put(cplng.getElementKey(), parts[1]);
                } else {
                    values.putNull(cplng.getElementKey());
                }
                if (parts.length > 2) {
                    values.put(cpalt.getElementKey(), parts[2]);
                } else {
                    values.putNull(cpalt.getElementKey());
                }
                if (parts.length > 3) {
                    values.put(cpacc.getElementKey(), parts[3]);
                } else {
                    values.putNull(cpacc.getElementKey());
                }
            }
        } else if (uriList.contains(cd)) {
            // find its children...
            List<ColumnDefinition> children = cd.getChildren();
            ColumnDefinition[] cdarray = new ColumnDefinition[children.size()];
            for (int i = 0; i < children.size(); ++i) {
                cdarray[i] = children.get(i);
            }
            // find the uriFragment
            ColumnDefinition cdfrag = null, cdtype = null;
            for (ColumnDefinition scp : cdarray) {
                if (scp.getElementName().equals("uriFragment")) {
                    cdfrag = scp;
                } else if (scp.getElementName().equals("contentType")) {
                    cdtype = scp;
                }
            }
            // update the uriFragment and contentType elements
            String value = formValues.formValues.get(cd.getElementKey());
            if (value == null || value.length() == 0) {
                values.putNull(cdfrag.getElementKey());
                values.putNull(cdtype.getElementKey());
            } else {
                int dotIdx = value.lastIndexOf(".");
                String ext = (dotIdx == -1) ? "*" : value.substring(dotIdx + 1);
                if (ext.length() == 0) {
                    ext = "*";
                }
                String baseContentType = cd.getElementName().substring(0, cd.getElementName().length() - 3);
                if (baseContentType.equals("mime")) {
                    baseContentType = "*";
                }
                String mimeType = baseContentType + "/" + ext;
                if (cd.getType().getDataType() == ElementDataType.configpath) {
                    values.put(cdfrag.getElementKey(), ODKFileUtils.asUriFragment(appName,
                            new File(ODKFileUtils.getAppFolder(appName), value)));
                    values.put(cdtype.getElementKey(), mimeType);
                } else {
                    File ifolder = new File(
                            ODKFileUtils.getInstanceFolder(appName, tableId, formValues.instanceID));
                    values.put(cdfrag.getElementKey(),
                            ODKFileUtils.asUriFragment(appName, new File(ifolder, value)));
                    values.put(cdtype.getElementKey(), mimeType);

                }
            }
        } else if (cd.isUnitOfRetention()) {

            ArrayList<Map<String, Object>> choices;
            SQLiteDatabase db = null;
            try {
                db = DatabaseFactory.get().getDatabase(context, appName);
                choices = (ArrayList<Map<String, Object>>) ColumnUtil.get().getDisplayChoicesList(db, tableId,
                        cd.getElementKey());
            } finally {
                if (db != null) {
                    db.close();
                }
            }
            String value = formValues.formValues.get(cd.getElementKey());
            value = ParseUtil.validifyValue(appName, du, choices, cd,
                    formValues.formValues.get(cd.getElementKey()));

            if (value != null) {
                values.put(cd.getElementKey(), value);
            } else {
                // don't we want to clear values too?
                values.putNull(cd.getElementKey());
            }
        }
    }
    return values;
}

From source file:org.getlantern.firetweet.util.Utils.java

public static boolean setLastSeen(Context context, long userId, long time) {
    final ContentResolver cr = context.getContentResolver();
    final ContentValues values = new ContentValues();
    if (time > 0) {
        values.put(CachedUsers.LAST_SEEN, time);
    } else {/* w w  w  .ja  v  a 2 s.  c o  m*/
        // Zero or negative value means remove last seen
        values.putNull(CachedUsers.LAST_SEEN);
    }
    final Expression where = Expression.equals(CachedUsers.USER_ID, userId);
    return cr.update(CachedUsers.CONTENT_URI, values, where.getSQL(), null) != 0;
}

From source file:com.vegnab.vegnab.MainVNActivity.java

void logPurchaseActivity(Purchase p, IabResult result, boolean isConsumed, String notes) {
    Uri uri, purchUri = Uri.withAppendedPath(ContentProvider_VegNab.CONTENT_URI, "purchases");
    ContentResolver rs = getContentResolver();
    ContentValues contentValues = new ContentValues();
    if (p == null) {
        contentValues.put("ProductIdCode", "(purchase object is null)");
        contentValues.put("Type", "null");
        contentValues.put("PurchaseState", -2); // purchase is null
    } else {/* w  w w  .jav a2s  . c  o  m*/
        String sku = p.getSku();
        contentValues.put("ProductIdCode", sku); // also called 'SKU'
        contentValues.put("DevPayload", p.getDeveloperPayload());
        contentValues.put("Type", p.getItemType()); // "inapp" for an in-app product or "subs" for subscriptions.
        contentValues.put("OrderIDCode", p.getOrderId());
        // corresponds to the Google payments order ID
        contentValues.put("PkgName", p.getPackageName());
        contentValues.put("Signature", p.getSignature());
        contentValues.put("Token", p.getToken());
        // uniquely identifies a purchase for a given item and user pair
        contentValues.put("PurchaseState", p.getPurchaseState());
        // standard: 0 (purchased), 1 (canceled), or 2 (refunded). or nonstandard: -1 (initiated), -2 (null)
        SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
        long t = p.getPurchaseTime();
        contentValues.put("PurchaseTime", dateTimeFormat.format(new Date(t)));
        contentValues.put("PurchJSON", p.getOriginalJson());
        try { // inventory object may not exist yet
            if (mInventory.hasDetails(sku)) {
                SkuDetails skuDetails = mInventory.getSkuDetails(sku);
                contentValues.put("Price", skuDetails.getPrice());
                contentValues.put("Description", skuDetails.getDescription());
                contentValues.put("Title", skuDetails.getTitle());
            } else {
                contentValues.putNull("Price");
                contentValues.putNull("Description");
                contentValues.putNull("Title");
            }
        } catch (Exception e) {
            contentValues.putNull("Price");
            contentValues.putNull("Description");
            contentValues.putNull("Title");
        }
    }

    contentValues.put("Consumed", isConsumed ? 1 : 0);
    if (result == null) {
        contentValues.putNull("IABResponse");
        contentValues.putNull("IABMessage");
    } else {
        contentValues.put("IABResponse", result.getResponse());
        contentValues.put("IABMessage", result.getMessage());
    }
    if (notes == null) {
        contentValues.putNull("Notes");
    } else {
        contentValues.put("Notes", notes);
    }
    // create a new record
    uri = rs.insert(purchUri, contentValues);
    mNewPurcRecId = Long.parseLong(uri.getLastPathSegment());
    if (LDebug.ON)
        Log.d(LOG_TAG, "mNewPurcRecId of new record stored in DB: " + mNewPurcRecId);
}

From source file:org.totschnig.myexpenses.MyApplication.java

/**
 * 1.check if a planner is configured. If no, nothing to do 2.check if the
 * configured planner exists on the device 2.1 if yes go through all events
 * and look for them based on UUID added to description recreate events that
 * we did not find (2.2 if no, user should have been asked to select a target
 * calendar where we will store the recreated events)
 *
 * @return Result with success true//  w  w  w  .  j ava  2 s. c o m
 */
public Result restorePlanner() {
    ContentResolver cr = getContentResolver();
    String TAG = "restorePlanner";
    String calendarId = PrefKey.PLANNER_CALENDAR_ID.getString("-1");
    String calendarPath = PrefKey.PLANNER_CALENDAR_PATH.getString("");
    Log.d(TAG, String.format("restore plans to calendar with id %s and path %s", calendarId, calendarPath));
    int restoredPlansCount = 0;
    if (!(calendarId.equals("-1") || calendarPath.equals(""))) {
        Cursor c = cr.query(Calendars.CONTENT_URI, new String[] { Calendars._ID },
                CALENDAR_FULL_PATH_PROJECTION + " = ?", new String[] { calendarPath }, null);
        if (c != null) {
            if (c.moveToFirst()) {
                mPlannerCalendarId = c.getString(0);
                Log.d(TAG, String.format("restorePlaner: found calendar with id %s", mPlannerCalendarId));
                PrefKey.PLANNER_CALENDAR_ID.putString(mPlannerCalendarId);
                ContentValues planValues = new ContentValues(), eventValues = new ContentValues();
                eventValues.put(Events.CALENDAR_ID, Long.parseLong(mPlannerCalendarId));
                Cursor planCursor = cr.query(Template.CONTENT_URI,
                        new String[] { DatabaseConstants.KEY_ROWID, DatabaseConstants.KEY_PLANID,
                                DatabaseConstants.KEY_UUID },
                        DatabaseConstants.KEY_PLANID + " IS NOT null", null, null);
                if (planCursor != null) {
                    if (planCursor.moveToFirst()) {
                        do {
                            long templateId = planCursor.getLong(0);
                            long oldPlanId = planCursor.getLong(1);
                            String uuid = planCursor.getString(2);
                            Cursor eventCursor = cr.query(Events.CONTENT_URI, new String[] { Events._ID },
                                    Events.CALENDAR_ID + " = ? AND " + Events.DESCRIPTION + " LIKE ?",
                                    new String[] { mPlannerCalendarId, "%" + uuid + "%" }, null);
                            if (eventCursor != null) {
                                if (eventCursor.moveToFirst()) {
                                    long newPlanId = eventCursor.getLong(0);
                                    Log.d(TAG,
                                            String.format(
                                                    "Looking for event with uuid %s: found id %d. "
                                                            + "Original event had id %d",
                                                    uuid, newPlanId, oldPlanId));
                                    if (newPlanId != oldPlanId) {
                                        planValues.put(DatabaseConstants.KEY_PLANID, newPlanId);
                                        int updated = cr.update(
                                                ContentUris.withAppendedId(Template.CONTENT_URI, templateId),
                                                planValues, null, null);
                                        if (updated > 0) {
                                            Log.i(TAG, "updated plan id in template:" + templateId);
                                            restoredPlansCount++;
                                        }
                                    } else {
                                        restoredPlansCount++;
                                    }
                                    continue;
                                }
                                eventCursor.close();
                            }
                            Log.d(TAG, String.format(
                                    "Looking for event with uuid %s did not find, now reconstructing from cache",
                                    uuid));
                            eventCursor = cr.query(TransactionProvider.EVENT_CACHE_URI, buildEventProjection(),
                                    Events.DESCRIPTION + " LIKE ?", new String[] { "%" + uuid + "%" }, null);
                            boolean found = false;
                            if (eventCursor != null) {
                                if (eventCursor.moveToFirst()) {
                                    found = true;
                                    copyEventData(eventCursor, eventValues);
                                    if (insertEventAndUpdatePlan(eventValues, templateId)) {
                                        Log.i(TAG, "updated plan id in template:" + templateId);
                                        restoredPlansCount++;
                                    }
                                }
                                eventCursor.close();
                            }
                            if (!found) {
                                //need to set eventId to null
                                planValues.putNull(DatabaseConstants.KEY_PLANID);
                                getContentResolver().update(
                                        ContentUris.withAppendedId(Template.CONTENT_URI, templateId),
                                        planValues, null, null);
                            }
                        } while (planCursor.moveToNext());
                    }
                    planCursor.close();
                }
            }
            c.close();
        }
    }
    return new Result(true, R.string.restore_calendar_success, restoredPlansCount);
}

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();
        }
    }
}

From source file:org.opendatakit.utilities.test.AbstractODKDatabaseUtilsTest.java

public void testInsertCheckpointRowIntoExistingTableWithIdWithRowConflictType_ExpectFail()
        throws ActionNotAuthorizedException {
    String tableId = testTable;/*from  w ww.j a v  a2s . com*/
    String testCol = "testColumn";
    String testColType = ElementDataType.string.name();
    String testVal = "test";
    String rowId = LocalizationUtils.genUUID();
    List<Column> columns = new ArrayList<Column>();
    columns.add(new Column(testCol, testCol, testColType, "[]"));
    OrderedColumns orderedColumns = ODKDatabaseImplUtils.get().createOrOpenTableWithColumns(db, tableId,
            columns);

    ContentValues cvValues = new ContentValues();
    cvValues.put(testCol, testVal);
    cvValues.putNull(DataTableColumns.CONFLICT_TYPE);

    boolean thrown = true;
    try {
        ODKDatabaseImplUtils.get().insertCheckpointRowWithId(db, tableId, orderedColumns, cvValues, rowId,
                activeUser, RoleConsts.ADMIN_ROLES_LIST, currentLocale);
    } catch (ActionNotAuthorizedException ex) {
        throw ex;
    } catch (Exception e) {
        thrown = true;
        e.printStackTrace();
    }

    assertTrue(thrown);

    // Drop the table now that the test is done
    ODKDatabaseImplUtils.get().deleteTableAndAllData(db, tableId);
}

From source file:org.opendatakit.utilities.test.AbstractODKDatabaseUtilsTest.java

public void testInsertCheckpointRowIntoExistingTableWithIdWithRowSavepointType_ExpectFail()
        throws ActionNotAuthorizedException {
    String tableId = testTable;/*w w w.  jav  a 2 s  . c om*/
    String testCol = "testColumn";
    String testColType = ElementDataType.string.name();
    String testVal = "test";
    String rowId = LocalizationUtils.genUUID();
    List<Column> columns = new ArrayList<Column>();
    columns.add(new Column(testCol, testCol, testColType, "[]"));
    OrderedColumns orderedColumns = ODKDatabaseImplUtils.get().createOrOpenTableWithColumns(db, tableId,
            columns);

    ContentValues cvValues = new ContentValues();
    cvValues.put(testCol, testVal);
    cvValues.putNull(DataTableColumns.SAVEPOINT_TYPE);

    boolean thrown = true;
    try {
        ODKDatabaseImplUtils.get().insertCheckpointRowWithId(db, tableId, orderedColumns, cvValues, rowId,
                activeUser, RoleConsts.ADMIN_ROLES_LIST, currentLocale);
    } catch (ActionNotAuthorizedException ex) {
        throw ex;
    } catch (Exception e) {
        thrown = true;
        e.printStackTrace();
    }

    assertTrue(thrown);

    // Drop the table now that the test is done
    ODKDatabaseImplUtils.get().deleteTableAndAllData(db, tableId);
}

From source file:org.opendatakit.utilities.test.AbstractODKDatabaseUtilsTest.java

public void testInsertCheckpointRowIntoExistingTableWithIdWithRowSavepointTimestamp_ExpectFail()
        throws ActionNotAuthorizedException {
    String tableId = testTable;/*from   www  .j  a  va2 s  . c  om*/
    String testCol = "testColumn";
    String testColType = ElementDataType.string.name();
    String testVal = "test";
    String rowId = LocalizationUtils.genUUID();
    List<Column> columns = new ArrayList<Column>();
    columns.add(new Column(testCol, testCol, testColType, "[]"));
    OrderedColumns orderedColumns = ODKDatabaseImplUtils.get().createOrOpenTableWithColumns(db, tableId,
            columns);

    ContentValues cvValues = new ContentValues();
    cvValues.put(testCol, testVal);
    cvValues.putNull(DataTableColumns.SAVEPOINT_TIMESTAMP);

    boolean thrown = true;
    try {
        ODKDatabaseImplUtils.get().insertCheckpointRowWithId(db, tableId, orderedColumns, cvValues, rowId,
                activeUser, RoleConsts.ADMIN_ROLES_LIST, currentLocale);
    } catch (ActionNotAuthorizedException ex) {
        throw ex;
    } catch (Exception e) {
        thrown = true;
        e.printStackTrace();
    }

    assertTrue(thrown);

    // Drop the table now that the test is done
    ODKDatabaseImplUtils.get().deleteTableAndAllData(db, tableId);
}

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

/**
 * If the latest row-level permissions from the server prevent the activeUser from
 * performing the modify or delete action on the row, immediately resolve the conflict
 * by taking the server's changes./*  ww  w .  ja va  2s .co  m*/
 *
 * If the latest row-level permissions from the server prevent the activeUser from
 * altering the permissions on the row, reset all of those permissions to match
 * the server's latest values.
 *
 * And, finally, 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.
 *
 * @param db
 * @param tableId
 * @param orderedColumns
 * @param rowId
 * @param initialLocalRowState
 * @param accessContext
 * @param locale
 * @return  true if we are still in conflict
 */
public boolean enforcePermissionsAndOptimizeConflictProcessing(OdkConnectionInterface db, String tableId,
        OrderedColumns orderedColumns, String rowId, SyncState initialLocalRowState,
        AccessContext accessContext, String locale) {

    // we should have two in-conflict records, on is the local, one is the server
    BaseTable baseTable = this.privilegedGetRowsWithId(db, tableId, rowId, accessContext.activeUser);
    if (baseTable.getNumberOfRows() != 2) {
        throw new IllegalStateException(
                "we should have exactly two rows -- one local-conflict and " + "one server-conflict row");
    }
    Integer idxServerRow = null;
    int serverRowConflictType = -1;
    Integer idxLocalRow = null;
    int localRowConflictType = -1;

    {
        for (int idx = 0; idx < 2; ++idx) {
            String rowConflictTypeStr = baseTable.getRowAtIndex(idx)
                    .getDataByKey(DataTableColumns.CONFLICT_TYPE);
            if (rowConflictTypeStr == 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 rowConflictType = Integer.parseInt(rowConflictTypeStr);
            if (rowConflictType == ConflictType.LOCAL_DELETED_OLD_VALUES
                    || rowConflictType == ConflictType.LOCAL_UPDATED_UPDATED_VALUES) {
                idxLocalRow = idx;
                localRowConflictType = rowConflictType;
            } else if (rowConflictType == ConflictType.SERVER_DELETED_OLD_VALUES
                    || rowConflictType == ConflictType.SERVER_UPDATED_UPDATED_VALUES) {
                idxServerRow = idx;
                serverRowConflictType = rowConflictType;
            }
        }

        if (idxServerRow == null) {
            throw new IllegalStateException(
                    "did not find server conflict row while optimizing " + "the conflict");
        }

        if (idxLocalRow == null) {
            throw new IllegalStateException(
                    "did not find local conflict row while optimizing " + "the conflict");
        }
    }

    Row serverRow = baseTable.getRowAtIndex(idxServerRow);
    String serverDefaultAccess = serverRow.getDataByKey(DataTableColumns.DEFAULT_ACCESS);
    String serverOwner = serverRow.getDataByKey(DataTableColumns.ROW_OWNER);
    String serverGroupReadOnly = serverRow.getDataByKey(DataTableColumns.GROUP_READ_ONLY);
    String serverGroupModify = serverRow.getDataByKey(DataTableColumns.GROUP_MODIFY);
    String serverGroupPrivileged = serverRow.getDataByKey(DataTableColumns.GROUP_PRIVILEGED);

    // Part 1: verify the ability to modify or delete the row fields (excluding permissions)

    // if the server changed privileges in the row such that this user does not have privileges
    // to modify (if local changed) or delete (if local deleted), then we can immediately
    // resolve to take server changes.
    TableSecuritySettings tss = getTableSecuritySettings(db, tableId);

    try {
        String updatedSyncState = (localRowConflictType == ConflictType.LOCAL_DELETED_OLD_VALUES)
                ? SyncState.deleted.name()
                : SyncState.changed.name();
        RowChange rowChange = (localRowConflictType == ConflictType.LOCAL_DELETED_OLD_VALUES)
                ? RowChange.DELETE_ROW
                : RowChange.CHANGE_ROW;

        tss.allowRowChange(accessContext.activeUser, accessContext.rolesArray, updatedSyncState,
                serverDefaultAccess, serverOwner, serverGroupReadOnly, serverGroupModify, serverGroupPrivileged,
                rowChange);

    } catch (ActionNotAuthorizedException e) {
        Row localRow = baseTable.getRowAtIndex(idxLocalRow);

        internalResolveServerConflictTakeServerRowWithId(db, tableId, rowId, orderedColumns,
                initialLocalRowState, serverRow, localRow, accessContext.activeUser, locale);
        return false;
    }

    // Part 2: Test if the permissions fields of the local row are different from any of
    // those from the server.
    //
    // If they are, and the local user is not able to modify permissions fields, silently
    // update the local in-conflict row to have the same permissions fields as the
    // server row.

    Row localRow = baseTable.getRowAtIndex(idxLocalRow);
    String localDefaultAccess = localRow.getDataByKey(DataTableColumns.DEFAULT_ACCESS);
    String localOwner = localRow.getDataByKey(DataTableColumns.ROW_OWNER);
    String localGroupReadOnly = localRow.getDataByKey(DataTableColumns.GROUP_READ_ONLY);
    String localGroupModify = localRow.getDataByKey(DataTableColumns.GROUP_MODIFY);
    String localGroupPrivileged = localRow.getDataByKey(DataTableColumns.GROUP_PRIVILEGED);

    if (!(sameValue(localDefaultAccess, serverDefaultAccess) && sameValue(localOwner, serverOwner)
            && sameValue(localGroupReadOnly, serverGroupReadOnly)
            && sameValue(localGroupModify, serverGroupModify)
            && sameValue(localGroupPrivileged, serverGroupPrivileged))) {

        // permissions columns have changed
        // test if we have permissions to make these changes
        try {
            tss.canModifyPermissions(accessContext.activeUser, accessContext.rolesArray, serverGroupPrivileged,
                    serverOwner);
        } catch (ActionNotAuthorizedException e) {

            // don't have permission to alter permissions columns --
            // update the row with all of the local columns as-is, but override all the
            // permissions fields with the values from the server.
            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));
                }
            }
            // take the server's permissions fields.
            if (serverDefaultAccess == null) {
                values.putNull(DataTableColumns.DEFAULT_ACCESS);
            } else {
                values.put(DataTableColumns.DEFAULT_ACCESS, serverDefaultAccess);
            }
            if (serverOwner == null) {
                values.putNull(DataTableColumns.ROW_OWNER);
            } else {
                values.put(DataTableColumns.ROW_OWNER, serverOwner);
            }
            if (serverGroupReadOnly == null) {
                values.putNull(DataTableColumns.GROUP_READ_ONLY);
            } else {
                values.put(DataTableColumns.GROUP_READ_ONLY, serverGroupReadOnly);
            }
            if (serverGroupModify == null) {
                values.putNull(DataTableColumns.GROUP_MODIFY);
            } else {
                values.put(DataTableColumns.GROUP_MODIFY, serverGroupModify);
            }
            if (serverGroupPrivileged == null) {
                values.putNull(DataTableColumns.GROUP_PRIVILEGED);
            } else {
                values.put(DataTableColumns.GROUP_PRIVILEGED, serverGroupPrivileged);
            }

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

    // at this point, all of the local row's changes are confirmed to be
    // able to be made by the active user. i.e.,
    //
    // If the local row is deleted, we could push a delete up to the server.
    //
    // If the local row is modified, we can push the modification up to the server.
    //
    // If the activeUser does not have permission to change the row's permissions,
    // all of those changes have been reverted to match the values on the server.
    return true;
}