// Copyright 2015 The Project Buendia Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy
// of the License at: http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distrib-
// uted under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
// OR CONDITIONS OF ANY KIND, either express or implied. See the License for
// specific language governing permissions and limitations under the License.
package org.projectbuendia.client.providers;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteStatement;
import org.projectbuendia.client.sync.Database;
import org.projectbuendia.client.sync.QueryBuilder;
/**
* A {@link ProviderDelegate} that provides query, insert, delete, and update access to a group or
* list of items provided directly from the database.
*/
class GroupProviderDelegate implements ProviderDelegate<Database> {
private static final String BULK_INSERT_SAVEPOINT = "GROUP_PROVIDER_DELEGATE_BULK_INSERT";
private final String mType;
private final Contracts.Table mTable;
public GroupProviderDelegate(String type, Contracts.Table table) {
mType = type;
mTable = table;
}
@Override public String getType() {
return mType;
}
@Override public Cursor query(
Database dbHelper, ContentResolver contentResolver, Uri uri,
String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor cursor = new QueryBuilder(mTable).where(selection, selectionArgs)
.orderBy(sortOrder)
.select(dbHelper.getReadableDatabase(), projection);
cursor.setNotificationUri(contentResolver, uri);
return cursor;
}
@Override public Uri insert(
Database dbHelper, ContentResolver contentResolver, Uri uri,
ContentValues values) {
long id = dbHelper.getWritableDatabase().replaceOrThrow(mTable.name, null, values);
contentResolver.notifyChange(uri, null, false);
return uri.buildUpon().appendPath(Long.toString(id)).build();
}
@Override public int bulkInsert(
Database dbHelper, ContentResolver contentResolver, Uri uri,
ContentValues[] allValues) {
if (allValues.length == 0) {
return 0;
}
final SQLiteDatabase db = dbHelper.getWritableDatabase();
SQLiteDatabaseTransactionHelper dbTransactionHelper =
new SQLiteDatabaseTransactionHelper(dbHelper);
ContentValues first = allValues[0];
String[] columns = first.keySet().toArray(new String[first.size()]);
SQLiteStatement statement = makeInsertStatement(db, mTable.name, columns);
dbTransactionHelper.startNamedTransaction(BULK_INSERT_SAVEPOINT);
try {
Object[] bindings = new Object[first.size()];
for (ContentValues values : allValues) {
statement.clearBindings();
if (values.size() != first.size()) {
throw new AssertionError();
}
for (int i = 0; i < bindings.length; i++) {
Object value = values.get(columns[i]);
// This isn't super safe, but is in our context.
int bindingIndex = i + 1;
if (value instanceof String) {
statement.bindString(bindingIndex, (String) value);
} else if ((value instanceof Long) || value instanceof Integer) {
statement.bindLong(bindingIndex, ((Number) value).longValue());
} else if ((value instanceof Double) || value instanceof Float) {
statement.bindDouble(bindingIndex, ((Number) value).doubleValue());
}
bindings[i] = value;
}
statement.executeInsert();
}
} catch (Throwable t) {
// If absolutely anything goes wrong, rollback to the savepoint.
dbTransactionHelper.rollbackNamedTransaction(BULK_INSERT_SAVEPOINT);
} finally {
statement.close();
}
dbTransactionHelper.releaseNamedTransaction(BULK_INSERT_SAVEPOINT);
contentResolver.notifyChange(uri, null, false);
return allValues.length;
}
private SQLiteStatement makeInsertStatement(
SQLiteDatabase db, String table, String[] columns) {
// I kind of hoped this would be provided by SQLiteDatabase or DatabaseHelper,
// But it doesn't seem to be. Innards copied from SQLiteDatabase.insertWithOnConflict
StringBuilder sql = new StringBuilder();
sql.append("INSERT OR REPLACE ");
sql.append(" INTO ");
sql.append(table);
sql.append('(');
int size = (columns != null && columns.length > 0) ? columns.length : 0;
if (size <= 0) {
throw new IllegalArgumentException();
}
for (int i = 0; i < columns.length; i++) {
sql.append((i > 0) ? "," : "");
sql.append(columns[i]);
}
sql.append(')');
sql.append(" VALUES (");
for (int i = 0; i < size; i++) {
sql.append((i > 0) ? ",?" : "?");
}
sql.append(')');
return db.compileStatement(sql.toString());
}
@Override public int delete(
Database dbHelper, ContentResolver contentResolver, Uri uri,
String selection, String[] selectionArgs) {
int count = new QueryBuilder(mTable)
.where(selection, selectionArgs)
.delete(dbHelper.getWritableDatabase());
contentResolver.notifyChange(uri, null, false);
return count;
}
@Override public int update(
Database dbHelper, ContentResolver contentResolver, Uri uri,
ContentValues values, String selection, String[] selectionArgs) {
int count = new QueryBuilder(mTable)
.where(selection, selectionArgs)
.update(dbHelper.getWritableDatabase(), values);
contentResolver.notifyChange(uri, null, false);
return count;
}
}