/**
* Copyright 2013-2016 Amazon.com,
* Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Amazon Software License (the "License").
* You may not use this file except in compliance with the
* License. A copy of the License is located at
*
* http://aws.amazon.com/asl/
*
* or in the "license" file accompanying this file. This file is
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, express or implied. See the License
* for the specific language governing permissions and
* limitations under the License.
*/
package com.amazonaws.mobileconnectors.cognito.internal.storage;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import com.amazonaws.mobileconnectors.cognito.Dataset;
import com.amazonaws.mobileconnectors.cognito.DatasetMetadata;
import com.amazonaws.mobileconnectors.cognito.Record;
import com.amazonaws.mobileconnectors.cognito.exceptions.DataStorageException;
import com.amazonaws.mobileconnectors.cognito.internal.util.DatasetUtils;
import com.amazonaws.mobileconnectors.cognito.internal.util.StringUtils;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
/**
* An SQLite implementation of {@link LocalStorage}. Datasets are stored in a
* SQLite database. There are two tables: 'datasets' table stores metadata of a
* dataset; 'records' stores records of a dataset.
*/
public class SQLiteLocalStorage implements LocalStorage {
private static final String TAG = "SQLiteLocalStorage";
/**
* Name of the datasets table. This table stores metadata of a dataset.
*/
static final String TABLE_DATASETS = "datasets";
/**
* Name of the records table. The table keeps a local copy of dataset
* records.
*/
static final String TABLE_RECORDS = "records";
/**
* A holder for the columns in datasets table.
*/
static class DatasetColumns {
static final String IDENTITY_ID = "identity_id";
static final String DATASET_NAME = "dataset_name";
static final String CREATION_TIMESTAMP = "creation_timestamp";
static final String LAST_MODIFIED_TIMESTAMP = "last_modified_timestamp";
static final String LAST_MODIFIED_BY = "last_modified_by";
static final String STORAGE_SIZE_BYTES = "storage_size_bytes";
static final String RECORD_COUNT = "record_count";
static final String LAST_SYNC_COUNT = "last_sync_count";
static final String LAST_SYNC_TIMESTAMP = "last_sync_timestamp";
static final String LAST_SYNC_RESULT = "last_sync_result";
/**
* Shortcut for all columns.
*/
static final String[] ALL = new String[] {
IDENTITY_ID, DATASET_NAME,
CREATION_TIMESTAMP, LAST_MODIFIED_TIMESTAMP, LAST_MODIFIED_BY,
STORAGE_SIZE_BYTES, RECORD_COUNT,
LAST_SYNC_COUNT, LAST_SYNC_TIMESTAMP, LAST_SYNC_RESULT,
};
}
/**
* A holder for the columns in records table.
*/
static class RecordColumns {
static final String IDENTITY_ID = "identity_id";
static final String DATASET_NAME = "dataset_name";
static final String KEY = "key";
static final String VALUE = "value";
static final String SYNC_COUNT = "sync_count";
static final String LAST_MODIFIED_TIMESTAMP = "last_modified_timestamp";
static final String LAST_MODIFIED_BY = "last_modified_by";
static final String DEVICE_LAST_MODIFIED_TIMESTAMP = "device_last_modified_timestamp";
static final String MODIFIED = "modified";
/**
* Shortcut for all columns.
*/
static final String[] ALL = new String[] {
IDENTITY_ID, DATASET_NAME, KEY, VALUE, SYNC_COUNT, LAST_MODIFIED_TIMESTAMP,
LAST_MODIFIED_BY, DEVICE_LAST_MODIFIED_TIMESTAMP, MODIFIED
};
}
/**
* This helper creates two tables per identity id: one to save the meta data
* of a dataset such as last server sync count and its status; the other to
* save a local copy of dataset.
*/
private class DatabaseHelper extends SQLiteOpenHelper {
private static final int DB_VERSION = 2;
public DatabaseHelper(Context context, String cognitoId) {
super(context.getApplicationContext(), cognitoId, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
// create datasets table
db.execSQL("CREATE TABLE " + TABLE_DATASETS + "("
+ DatasetColumns.IDENTITY_ID + " TEXT NOT NULL,"
+ DatasetColumns.DATASET_NAME + " TEXT NOT NULL,"
+ DatasetColumns.CREATION_TIMESTAMP + " INTEGER DEFAULT 0,"
+ DatasetColumns.LAST_MODIFIED_TIMESTAMP + " INTEGER DEFAULT 0,"
+ DatasetColumns.LAST_MODIFIED_BY + " TEXT,"
+ DatasetColumns.STORAGE_SIZE_BYTES + " INTEGER DEFAULT 0,"
+ DatasetColumns.RECORD_COUNT + " INTEGER DEFAULT 0,"
+ DatasetColumns.LAST_SYNC_COUNT + " INTEGER NOT NULL DEFAULT 0,"
+ DatasetColumns.LAST_SYNC_TIMESTAMP + " INTEGER DEFAULT 0,"
+ DatasetColumns.LAST_SYNC_RESULT + " TEXT,"
+ "UNIQUE (" + DatasetColumns.IDENTITY_ID + ", "
+ DatasetColumns.DATASET_NAME + ")"
+ ")");
// create records table
db.execSQL("CREATE TABLE " + TABLE_RECORDS + "("
+ RecordColumns.IDENTITY_ID + " TEXT NOT NULL,"
+ RecordColumns.DATASET_NAME + " TEXT NOT NULL,"
+ RecordColumns.KEY + " TEXT NOT NULL,"
+ RecordColumns.VALUE + " TEXT,"
+ RecordColumns.SYNC_COUNT + " INTEGER NOT NULL DEFAULT 0,"
+ RecordColumns.LAST_MODIFIED_TIMESTAMP + " INTEGER DEFAULT 0,"
+ RecordColumns.LAST_MODIFIED_BY + " TEXT,"
+ RecordColumns.DEVICE_LAST_MODIFIED_TIMESTAMP + " INTEGER DEFAULT 0,"
+ RecordColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 1,"
+ "UNIQUE (" + RecordColumns.IDENTITY_ID + ", " + RecordColumns.DATASET_NAME
+ ", " + RecordColumns.KEY + ")"
+ ")");
}
/**
* Upgrading database will wipe all data. Any changes that haven't been
* synced will be lost.
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Upgrading database; wiping all data");
db.execSQL("DROP TABLE IF EXISTS " + TABLE_DATASETS);
db.execSQL("DROP TABLE IF EXISTS " + TABLE_RECORDS);
onCreate(db);
}
}
/**
* A database helper.
*/
private final DatabaseHelper helper;
/**
* Creates a {@link SQLiteLocalStorage} object.
*
* @param context a context of the app
* @param databaseName name of the database
*/
public SQLiteLocalStorage(Context context, String databaseName) {
helper = new DatabaseHelper(context, databaseName);
}
/**
* Creates a new {@link Dataset}. Stores its information in datasets table.
* Nothing will happen if a dataset with the same name exists. This should
* be called before any operation related to this dataset name is performed.
* Otherwise this dataset won't be visible in listDatasets and will result
* in dangled records.
*
* @param identityId identity id
* @param datasetName dataset name
*/
public void createDataset(String identityId, String datasetName) {
SQLiteDatabase db = helper.getWritableDatabase();
db.beginTransaction();
try {
// check if the dataset exists or not
DatasetMetadata metadata = getMetadataInternal(db, identityId, datasetName);
if (metadata == null) {
// add a new entry if it doesn't exist
ContentValues values = new ContentValues();
values.put(DatasetColumns.IDENTITY_ID, identityId);
values.put(DatasetColumns.DATASET_NAME, datasetName);
values.put(DatasetColumns.CREATION_TIMESTAMP,
String.valueOf(new Date().getTime()));
values.put(DatasetColumns.LAST_MODIFIED_TIMESTAMP,
String.valueOf(new Date().getTime()));
// use default values for other fields
long row = db.insert(TABLE_DATASETS, null, values);
if (row == -1) {
Log.e(TAG, String.format("couldn't create dataset %s",
datasetName));
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
@Override
public String getValue(String identityId, String datasetName, String key) {
Record record = getRecord(identityId, datasetName, key);
if (record == null) {
return null;
} else {
return record.getValue();
}
}
@Override
public void putValue(String identityId, String datasetName, String key, String value) {
SQLiteDatabase db = helper.getWritableDatabase();
boolean result = putValueInternal(db, identityId, datasetName, key, value);
if (!result) {
Log.e(TAG, String.format("failed to put record [%s: %s] into dataset %s",
key, value, datasetName));
} else {
updateLastModifiedTimestamp(db, identityId, datasetName);
}
}
@Override
public Map<String, String> getValueMap(String identityId, String datasetName) {
Map<String, String> values = new HashMap<String, String>();
List<Record> records = getRecords(identityId, datasetName);
for (Record record : records) {
if (!record.isDeleted()) {
values.put(record.getKey(), record.getValue());
}
}
return values;
}
@Override
public void putAllValues(String identityId, String datasetName, Map<String, String> values) {
SQLiteDatabase db = helper.getWritableDatabase();
db.beginTransaction();
try {
for (Map.Entry<String, String> entry : values.entrySet()) {
putValueInternal(db, identityId, datasetName, entry.getKey(), entry.getValue());
}
updateLastModifiedTimestamp(db, identityId, datasetName);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
/**
* Puts key value into database. If the value is the same as the cached
* value, nothing will be updated and true is returned.
*
* @param db sqlite database handler
* @param datasetName dataset name
* @param key key of record
* @param value value of record
* @return true if put successfully, false otherwise
*/
boolean putValueInternal(SQLiteDatabase db, String identityId, String datasetName,
String key, String value) {
Record record = getRecord(identityId, datasetName, key);
// compares value before putting it in database. If yes, return true.
if (record != null && StringUtils.equals(record.getValue(), value)) {
return true;
}
ContentValues values = new ContentValues();
values.put(RecordColumns.IDENTITY_ID, identityId);
values.put(RecordColumns.DATASET_NAME, datasetName);
values.put(RecordColumns.KEY, key);
values.put(RecordColumns.VALUE, value);
values.put(RecordColumns.MODIFIED, true);
values.put(RecordColumns.SYNC_COUNT, record == null ? 0 : record.getSyncCount());
values.put(RecordColumns.DEVICE_LAST_MODIFIED_TIMESTAMP,
String.valueOf(new Date().getTime()));
if (record == null) {
return db.insert(TABLE_RECORDS, null, values) != -1;
} else {
return db.update(TABLE_RECORDS, values,
RecordColumns.IDENTITY_ID + " = ? AND " + RecordColumns.DATASET_NAME
+ " = ? AND " + RecordColumns.KEY + " = ?",
new String[] {
identityId, datasetName, key
}) == 1;
}
}
@Override
public List<DatasetMetadata> getDatasets(String identityId) {
List<DatasetMetadata> datasets = new ArrayList<DatasetMetadata>();
SQLiteDatabase db = helper.getWritableDatabase();
Cursor c = db.query(TABLE_DATASETS, DatasetColumns.ALL,
DatasetColumns.IDENTITY_ID + " = ?",
new String[] {
identityId
}, null, null, null);
while (c.moveToNext()) {
datasets.add(cursorToDatasetMetadata(c));
}
c.close();
return datasets;
}
@Override
public DatasetMetadata getDatasetMetadata(String identityId, String datasetName)
throws DataStorageException {
DatasetMetadata dataset = null;
SQLiteDatabase db = helper.getWritableDatabase();
Cursor c = db.query(TABLE_DATASETS, DatasetColumns.ALL,
DatasetColumns.IDENTITY_ID + " = ? AND " + DatasetColumns.DATASET_NAME + " = ?",
new String[] {
identityId, datasetName
}, null, null, null);
if (c.moveToFirst()) {
dataset = cursorToDatasetMetadata(c);
}
c.close();
return dataset;
}
DatasetMetadata cursorToDatasetMetadata(Cursor c) {
String datasetName = c.getString(c.getColumnIndex(DatasetColumns.DATASET_NAME));
return new DatasetMetadata.Builder(datasetName)
.creationDate(new Date(c.getLong(
c.getColumnIndex(DatasetColumns.CREATION_TIMESTAMP))))
.lastModifiedDate(new Date(c.getLong(
c.getColumnIndex(DatasetColumns.LAST_MODIFIED_TIMESTAMP))))
.lastModifiedBy(c.getString(c.getColumnIndex(DatasetColumns.LAST_MODIFIED_BY)))
.storageSizeBytes(c.getLong(c.getColumnIndex(DatasetColumns.STORAGE_SIZE_BYTES)))
.recordCount(c.getLong(c.getColumnIndex(DatasetColumns.RECORD_COUNT)))
.build();
}
@Override
public Record getRecord(String identityId, String datasetName, String key) {
Record record = null;
SQLiteDatabase db = helper.getWritableDatabase();
Cursor c = db.query(TABLE_RECORDS, RecordColumns.ALL,
RecordColumns.IDENTITY_ID + " = ? AND "
+ RecordColumns.DATASET_NAME + " = ? AND "
+ RecordColumns.KEY + " = ?",
new String[] {
identityId, datasetName, key
}, null, null, null);
if (c.moveToFirst()) {
record = cursorToRecord(c);
}
c.close();
return record;
}
@Override
public List<Record> getRecords(String identityId, String datasetName) {
List<Record> records = new ArrayList<Record>();
SQLiteDatabase db = helper.getWritableDatabase();
Cursor c = db.query(TABLE_RECORDS, RecordColumns.ALL,
RecordColumns.IDENTITY_ID + " = ? AND "
+ RecordColumns.DATASET_NAME + " = ?",
new String[] {
identityId, datasetName
}, null, null, null);
while (c.moveToNext()) {
records.add(cursorToRecord(c));
}
c.close();
return records;
}
/**
* Converts a Cursor object into a Record object. This could potentially
* throws exception if the stored record has bogus data that fails the
* constructor.
*
* @param c a Cursor object
* @return a Record object.
*/
Record cursorToRecord(Cursor c) {
return new Record.Builder(c.getString(c.getColumnIndex(RecordColumns.KEY)))
.value(c.getString(c.getColumnIndex(RecordColumns.VALUE)))
.syncCount(c.getLong(c.getColumnIndex(RecordColumns.SYNC_COUNT)))
.deviceLastModifiedDate(
new Date(c.getLong(c
.getColumnIndex(RecordColumns.DEVICE_LAST_MODIFIED_TIMESTAMP))))
.lastModifiedDate(
new Date(c.getLong(c.getColumnIndex(RecordColumns.LAST_MODIFIED_TIMESTAMP))))
.lastModifiedBy(c.getString(c.getColumnIndex(RecordColumns.LAST_MODIFIED_BY)))
.modified(c.getInt(c.getColumnIndex(RecordColumns.MODIFIED)) == 1)
.build();
}
@Override
public void putRecords(String identityId, String datasetName, List<Record> records) {
SQLiteDatabase db = helper.getWritableDatabase();
db.beginTransaction();
try {
for (Record record : records) {
updateOrInsertRecord(db, identityId, datasetName, record);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
@Override
public void conditionallyPutRecords(String identityId, String datasetName, List<Record> records,
List<Record> localRecords) {
// Build up an easy way to check the records from the snapshot of the db changes taken mid sync
Map<String, Record> localRecordMap = new HashMap<String, Record>();
for(Record record : localRecords){
localRecordMap.put(record.getKey(), record);
}
SQLiteDatabase db = helper.getWritableDatabase();
db.beginTransaction();
try {
for (Record record : records) {
/*
* Grab an instance of the record from the local store with the remote change's
* key and the snapshot version.
* 1) If both are null the remote change is new and we should save.
* 2) If both exist but the values and sync counts have changed,
* it has changed locally and we shouldn't overwrite with the remote changes,
* which will still exist in remote.
* 3) If both exist and the values have not changed, we should save the remote change.
* 4) If the current check exists but it wasn't in the snapshot, we should save.
*/
Record databaseRecord = getRecord(identityId, datasetName, record.getKey());
Record oldDatabaseRecord = localRecordMap.get(record.getKey());
if (databaseRecord != null && oldDatabaseRecord != null
&& (!StringUtils.equals(databaseRecord.getValue(), oldDatabaseRecord.getValue())
|| databaseRecord.getSyncCount() != oldDatabaseRecord.getSyncCount()
|| !StringUtils.equals(databaseRecord.getLastModifiedBy(), oldDatabaseRecord.getLastModifiedBy()))) {
continue;
}
updateOrInsertRecord(db, identityId, datasetName, record);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
@Override
public void deleteDataset(String identityId, String datasetName) {
// drop dataset table, and update metadata accordingly
SQLiteDatabase db = helper.getWritableDatabase();
db.beginTransaction();
try {
int rows = db.delete(TABLE_RECORDS,
RecordColumns.IDENTITY_ID + " = ? AND " + RecordColumns.DATASET_NAME + " = ?",
new String[] {
identityId, datasetName
});
Log.i(TAG, String.format("%d records were deleted from dataset %s",
rows, datasetName));
ContentValues values = new ContentValues();
values.put(DatasetColumns.LAST_MODIFIED_TIMESTAMP, System.currentTimeMillis());
values.put(DatasetColumns.LAST_SYNC_COUNT, String.valueOf(-1));
rows = db.update(TABLE_DATASETS, values,
DatasetColumns.IDENTITY_ID + " = ? AND "
+ DatasetColumns.DATASET_NAME + " = ?",
new String[] {
identityId, datasetName
});
if (rows != 1) {
Log.e(TAG, String.format("couldn't mark dataset %s deleted",
datasetName));
} else {
db.setTransactionSuccessful();
}
} finally {
db.endTransaction();
}
}
@Override
public void purgeDataset(String identityId, String datasetName) {
deleteDataset(identityId, datasetName);
SQLiteDatabase db = helper.getWritableDatabase();
int rows = db.delete(TABLE_DATASETS,
DatasetColumns.IDENTITY_ID + " = ? AND " + DatasetColumns.DATASET_NAME + " = ?",
new String[] {
identityId, datasetName
});
if (rows != 1) {
Log.e(TAG, String.format("failed to purge dataset %s", datasetName));
}
}
@Override
public long getLastSyncCount(String identityId, String datasetName) {
long lastSyncCount = 0;
SQLiteDatabase db = helper.getWritableDatabase();
Cursor c = db.query(TABLE_DATASETS, DatasetColumns.ALL,
DatasetColumns.IDENTITY_ID + " = ? AND " + DatasetColumns.DATASET_NAME + " = ?",
new String[] {
identityId, datasetName
}, null, null, null);
if (c.moveToFirst()) {
lastSyncCount = c.getInt(c.getColumnIndex(DatasetColumns.LAST_SYNC_COUNT));
} else {
Log.w(TAG, String.format("couldn't find %s in %s table",
datasetName, TABLE_DATASETS));
}
c.close();
return lastSyncCount;
}
@Override
public List<Record> getModifiedRecords(String identityId, String datasetName) {
List<Record> records = new ArrayList<Record>();
SQLiteDatabase db = helper.getWritableDatabase();
Cursor c = db.query(TABLE_RECORDS, RecordColumns.ALL,
RecordColumns.IDENTITY_ID + " = ? AND " + RecordColumns.DATASET_NAME + " = ? AND "
+ RecordColumns.MODIFIED + " = ?",
new String[] {
identityId, datasetName,
String.valueOf(1)
}, null, null, null);
while (c.moveToNext()) {
records.add(cursorToRecord(c));
}
c.close();
return records;
}
/**
* Close the local storage. Any operation called after this will fail.
*/
public void close() {
helper.close();
}
@Override
public void updateLastSyncCount(String identityId, String datasetName, long lastSyncCount) {
SQLiteDatabase db = helper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(DatasetColumns.LAST_SYNC_COUNT, lastSyncCount);
values.put(DatasetColumns.LAST_SYNC_TIMESTAMP, System.currentTimeMillis());
int rows = db.update(TABLE_DATASETS, values,
DatasetColumns.IDENTITY_ID + " = ? AND " + DatasetColumns.DATASET_NAME + " = ?",
new String[] {
identityId, datasetName
});
if (rows != 1) {
Log.e(TAG, String.format("failed to update last sync count of dataset %s",
datasetName));
}
}
@Override
public void wipeData() {
SQLiteDatabase db = helper.getWritableDatabase();
db.beginTransaction();
try {
db.execSQL("DELETE FROM " + TABLE_DATASETS);
db.execSQL("DELETE FROM " + TABLE_RECORDS);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
/**
* Updates last modified timestamp of a record. It's usually updated when a
* record value is set.
*
* @param datasetName dataset name
*/
void updateLastModifiedTimestamp(SQLiteDatabase db, String identityId, String datasetName) {
ContentValues values = new ContentValues();
values.put(DatasetColumns.LAST_MODIFIED_TIMESTAMP, System.currentTimeMillis());
int rows = db.update(TABLE_DATASETS, values,
DatasetColumns.IDENTITY_ID + " = ? AND " + DatasetColumns.DATASET_NAME + " = ?",
new String[] {
identityId, datasetName
});
if (rows != 1) {
Log.w(TAG, String.format("failed to update last modified timestamp of dataset %s",
datasetName));
}
}
/**
* Remove a record from database. It doesn't matter whether the record has
* local change.
*
* @param db SQLiteDatabase handler
* @param datasetName dataset name
* @param record the record to be removed
*/
void removeRecord(SQLiteDatabase db, String identityId, String datasetName, Record record) {
int rows = db.delete(TABLE_RECORDS,
RecordColumns.IDENTITY_ID + " = ? AND " + RecordColumns.DATASET_NAME + " = ? AND "
+ RecordColumns.KEY + " = ?",
new String[] {
identityId, datasetName, record.getKey()
});
if (rows == 0) {
Log.w(TAG, String.format("failed to delete record %s from dataset %s",
record.getKey(), datasetName));
}
}
/**
* Updates dataset with the given record and clears the modified flag. This
* is used to store remote change into local.
*
* @param db SQLiteDatabase handler
* @param identityId identity id of the dataset
* @param datasetName dataset name
* @param record the record to be updated
*/
void updateOrInsertRecord(SQLiteDatabase db, String identityId, String datasetName,
Record record) {
ContentValues values = new ContentValues();
values.put(RecordColumns.IDENTITY_ID, identityId);
values.put(RecordColumns.DATASET_NAME, datasetName);
values.put(RecordColumns.KEY, record.getKey());
values.put(RecordColumns.VALUE, record.getValue());
values.put(RecordColumns.SYNC_COUNT, record.getSyncCount());
values.put(RecordColumns.MODIFIED, record.isModified());
values.put(RecordColumns.LAST_MODIFIED_BY, record.getLastModifiedBy());
if(record.getLastModifiedDate() != null){
values.put(RecordColumns.LAST_MODIFIED_TIMESTAMP,
String.valueOf(record.getLastModifiedDate().getTime()));
}
if(record.getDeviceLastModifiedDate() != null){
values.put(RecordColumns.DEVICE_LAST_MODIFIED_TIMESTAMP,
String.valueOf(record.getDeviceLastModifiedDate().getTime()));
}
long row = db.replace(TABLE_RECORDS, null, values);
if (row == -1) {
Log.e(TAG, String.format("failed to store record [%s: %s] into dataset %s",
record.getKey(), record.getValue(), datasetName));
}
}
/**
* Checks the metadata of all datasets for one identity id and another to
* see if any have the same name.
*
* @param oldIdentityId the old identity id, which the user is changing from
* @param newIdentityId the new identity id, which the user is changing to
* @return a set of all of the non unique names
*/
Set<String> getCommonDatasetNames(String oldIdentityId, String newIdentityId){
Set<String> newNameSet = new HashSet<String>();
Set<String> oldNameSet = new HashSet<String>();
if(oldIdentityId != null && newIdentityId != null){
List<DatasetMetadata> newDatasets = getDatasets(newIdentityId);
List<DatasetMetadata> oldDatasets = getDatasets(oldIdentityId);
for(DatasetMetadata oldMetaData : oldDatasets){
oldNameSet.add(oldMetaData.getDatasetName());
}
for(DatasetMetadata newMetaData : newDatasets){
newNameSet.add(newMetaData.getDatasetName());
}
oldNameSet.retainAll(newNameSet);
}
return oldNameSet;
}
@Override
public synchronized void changeIdentityId(String oldIdentityId, String newIdentityId) {
SQLiteDatabase db = helper.getWritableDatabase();
db.beginTransaction();
try {
// if oldIdentityId is unknown, aka the dataset is created prior to
// having a cognito id, just reparent datasets from unknown to
// newIdentityId
if (DatasetUtils.UNKNOWN_IDENTITY_ID.equals(oldIdentityId)) {
Set<String> commonDatasetNames = getCommonDatasetNames(oldIdentityId, newIdentityId);
// append UNKNOWN to the name of all non unique datasets
if(commonDatasetNames.size() > 0){
for(String oldDatasetName : commonDatasetNames){
db.execSQL("UPDATE " + TABLE_DATASETS
+ " SET "
+ DatasetColumns.DATASET_NAME + " = '" + oldDatasetName + "." + oldIdentityId + "'"
+ " WHERE " + DatasetColumns.IDENTITY_ID + " = ?"
+ " AND " + DatasetColumns.DATASET_NAME + " = ?",
new String[] {
oldIdentityId,
oldDatasetName
});
db.execSQL("UPDATE " + TABLE_RECORDS
+ " SET "
+ RecordColumns.DATASET_NAME + " = '" + oldDatasetName + "." + oldIdentityId + "'"
+ " WHERE " + RecordColumns.IDENTITY_ID + " = ?"
+ " AND " + RecordColumns.DATASET_NAME + " = ?",
new String[] {
oldIdentityId,
oldDatasetName
});
}
}
// datasets table
db.execSQL("UPDATE " + TABLE_DATASETS
+ " SET "
+ DatasetColumns.IDENTITY_ID + " = '" + newIdentityId + "'"
+ " WHERE " + DatasetColumns.IDENTITY_ID + " = ?",
new String[] {
oldIdentityId
});
// records table
db.execSQL("UPDATE " + TABLE_RECORDS
+ " SET "
+ RecordColumns.IDENTITY_ID + " = '" + newIdentityId + "'"
+ " WHERE " + RecordColumns.IDENTITY_ID + " = ?",
new String[] {
oldIdentityId
});
} else {
// 1. copy oldIdentityId/dataset to newIdentityId/dataset
// datasets table
db.execSQL("INSERT INTO " + TABLE_DATASETS + "("
+ DatasetColumns.IDENTITY_ID + ","
+ DatasetColumns.DATASET_NAME + ","
+ DatasetColumns.CREATION_TIMESTAMP + ","
+ DatasetColumns.STORAGE_SIZE_BYTES + ","
+ DatasetColumns.RECORD_COUNT
// last sync count is reset to default 0
+ ")"
+ " SELECT "
+ "'" + newIdentityId + "'," // assign new owner
+ DatasetColumns.DATASET_NAME + ","
+ DatasetColumns.CREATION_TIMESTAMP + ","
+ DatasetColumns.STORAGE_SIZE_BYTES + ","
+ DatasetColumns.RECORD_COUNT
+ " FROM " + TABLE_DATASETS
+ " WHERE " + DatasetColumns.IDENTITY_ID + " = ?",
new String[] {
oldIdentityId
});
// records table
db.execSQL("INSERT INTO " + TABLE_RECORDS + "("
+ RecordColumns.IDENTITY_ID + ","
+ RecordColumns.DATASET_NAME + ","
+ RecordColumns.KEY + ","
+ RecordColumns.VALUE + ","
// sync count is resset to default 0
+ RecordColumns.LAST_MODIFIED_TIMESTAMP + ","
+ RecordColumns.LAST_MODIFIED_BY + ","
+ RecordColumns.DEVICE_LAST_MODIFIED_TIMESTAMP
// modified is reset to default 1 (dirty)
+ ")"
+ " SELECT "
+ "'" + newIdentityId + "'," // assign new owner
+ RecordColumns.DATASET_NAME + ","
+ RecordColumns.KEY + ","
+ RecordColumns.VALUE + ","
+ RecordColumns.LAST_MODIFIED_TIMESTAMP + ","
+ RecordColumns.LAST_MODIFIED_BY + ","
+ RecordColumns.DEVICE_LAST_MODIFIED_TIMESTAMP
+ " FROM " + TABLE_RECORDS
+ " WHERE " + RecordColumns.IDENTITY_ID + " = ?",
new String[] {
oldIdentityId
});
// 2. rename oldIdentityId/dataset to
// newIdentityId/dataset.oldIdentityId
// datasets table
db.execSQL("UPDATE " + TABLE_DATASETS
+ " SET "
+ DatasetColumns.IDENTITY_ID + " = '" + newIdentityId + "', "
+ DatasetColumns.DATASET_NAME + " = "
+ DatasetColumns.DATASET_NAME + " || '." + oldIdentityId + "'"
+ " WHERE " + DatasetColumns.IDENTITY_ID + " = ?",
new String[] {
oldIdentityId
});
// records table
db.execSQL("UPDATE " + TABLE_RECORDS
+ " SET "
+ RecordColumns.IDENTITY_ID + " = '" + newIdentityId + "', "
+ RecordColumns.DATASET_NAME + " = "
+ RecordColumns.DATASET_NAME + " || '." + oldIdentityId + "'"
+ " WHERE " + RecordColumns.IDENTITY_ID + " = ?",
new String[] {
oldIdentityId
});
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
@Override
public void updateDatasetMetadata(String identityId, List<DatasetMetadata> datasetMetadata) {
SQLiteDatabase db = helper.getWritableDatabase();
db.beginTransaction();
try {
for (DatasetMetadata metadata : datasetMetadata) {
if (!updateDatasetMetadataInternal(db, identityId, metadata)) {
Log.w(TAG, String.format("couldn't update dataset metadata %s",
metadata.getDatasetName()));
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
/**
* Internal helper function to get dataset metadata
*
* @param db SQLiteDatabase handler
* @param identityId identity id of the datasets
* @param datasetName dataset name
* @return DatasetMetadata if found, or null if not found
*/
DatasetMetadata getMetadataInternal(SQLiteDatabase db, String identityId, String datasetName) {
DatasetMetadata metadata = null;
Cursor c = db.query(TABLE_DATASETS, DatasetColumns.ALL,
DatasetColumns.IDENTITY_ID + " = ? AND " + DatasetColumns.DATASET_NAME + " = ?",
new String[] {
identityId, datasetName
}, null, null, null);
if (c.moveToFirst()) {
metadata = cursorToDatasetMetadata(c);
}
c.close();
return metadata;
}
/**
* Internal helper function to update one dataset metadata.
*
* @param db SQLiteDatabase handler
* @param identityId identity id of the datasets
* @param metadata dataset metadata to update
* @return true if update successfully, false otherwise
*/
boolean updateDatasetMetadataInternal(SQLiteDatabase db, String identityId,
DatasetMetadata metadata) {
DatasetMetadata local = getMetadataInternal(db, identityId, metadata.getDatasetName());
ContentValues values = new ContentValues();
values.put(DatasetColumns.IDENTITY_ID, identityId);
values.put(DatasetColumns.DATASET_NAME, metadata.getDatasetName());
values.put(DatasetColumns.CREATION_TIMESTAMP, metadata.getCreationDate().getTime());
values.put(DatasetColumns.LAST_MODIFIED_TIMESTAMP, metadata.getLastModifiedDate().getTime());
values.put(DatasetColumns.LAST_MODIFIED_BY, metadata.getLastModifiedBy());
values.put(DatasetColumns.RECORD_COUNT, metadata.getRecordCount());
values.put(DatasetColumns.STORAGE_SIZE_BYTES, metadata.getStorageSizeBytes());
if (local == null) {
return db.insert(TABLE_DATASETS, null, values) != -1;
} else {
return db.update(TABLE_DATASETS, values,
DatasetColumns.IDENTITY_ID + " = ? AND "
+ DatasetColumns.DATASET_NAME + " = ?",
new String[] {
identityId, metadata.getDatasetName()
}) == 1;
}
}
}