package org.commcare.models.legacy; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.util.Pair; import org.commcare.models.database.AndroidTableBuilder; import org.commcare.models.database.SqlStorage; import org.commcare.models.database.SqlStorageIterator; import org.commcare.modern.database.DatabaseHelper; import org.commcare.modern.models.EncryptedModel; import org.javarosa.core.services.storage.EntityFilter; import org.javarosa.core.services.storage.IStorageIterator; import org.javarosa.core.services.storage.Persistable; import org.javarosa.core.util.InvalidIndexException; import org.javarosa.core.util.externalizable.DeserializationException; import org.javarosa.core.util.externalizable.Externalizable; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Vector; /** * @author ctsims */ public class LegacySqlIndexedStorageUtility<T extends Persistable> extends SqlStorage<T> { private final String table; private final Class<? extends T> ctype; private EncryptedModel em; private T t; private final LegacyDbHelper helper; public LegacySqlIndexedStorageUtility(String table, Class<? extends T> ctype, LegacyDbHelper helper) { this.table = table; this.ctype = ctype; this.helper = helper; try { T e = ctype.newInstance(); if (e instanceof EncryptedModel) { em = (EncryptedModel)e; } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } } @Override public Vector<Integer> getIDsForValue(String fieldName, Object value) { return getIDsForValues(new String[]{fieldName}, new Object[]{value}); } @Override public Vector<Integer> getIDsForValues(String[] fieldNames, Object[] values) { Pair<String, String[]> whereClause = helper.createWhere(fieldNames, values, em, t); Cursor c = helper.getHandle().query(table, new String[]{DatabaseHelper.ID_COL}, whereClause.first, whereClause.second, null, null, null); if (c.getCount() == 0) { c.close(); return new Vector<>(); } else { c.moveToFirst(); Vector<Integer> indices = new Vector<>(); int index = c.getColumnIndexOrThrow(DatabaseHelper.ID_COL); while (!c.isAfterLast()) { int id = c.getInt(index); indices.add(Integer.valueOf(id)); c.moveToNext(); } c.close(); return indices; } } @Override public Vector<T> getRecordsForValues(String[] fieldNames, Object[] values) { Pair<String, String[]> whereClause = helper.createWhere(fieldNames, values, em, t); Cursor c = helper.getHandle().query(table, new String[]{DatabaseHelper.DATA_COL}, whereClause.first, whereClause.second, null, null, null); if (c.getCount() == 0) { c.close(); return new Vector<>(); } else { c.moveToFirst(); Vector<T> indices = new Vector<>(); int index = c.getColumnIndexOrThrow(DatabaseHelper.DATA_COL); while (!c.isAfterLast()) { byte[] data = c.getBlob(index); indices.add(newObject(data)); c.moveToNext(); } c.close(); return indices; } } @Override public String getMetaDataFieldForRecord(int recordId, String rawFieldName) { String rid = String.valueOf(recordId); String scrubbedName = AndroidTableBuilder.scrubName(rawFieldName); Cursor c = helper.getHandle().query(table, new String[]{scrubbedName}, DatabaseHelper.ID_COL + "=?", new String[]{rid}, null, null, null); if (c.getCount() == 0) { c.close(); throw new NoSuchElementException("No record in table " + table + " for ID " + recordId); } c.moveToFirst(); String result = c.getString(c.getColumnIndexOrThrow(scrubbedName)); c.close(); return result; } @Override public T getRecordForValues(String[] rawFieldNames, Object[] values) throws NoSuchElementException, InvalidIndexException { Pair<String, String[]> whereClause = helper.createWhere(rawFieldNames, values, em, t); Cursor c = helper.getHandle().query(table, new String[]{DatabaseHelper.ID_COL, DatabaseHelper.DATA_COL}, whereClause.first, whereClause.second, null, null, null); if (c.getCount() == 0) { throw new NoSuchElementException("No element in table " + table + " with names " + rawFieldNames + " and values " + values.toString()); } if (c.getCount() > 1) { throw new InvalidIndexException("Invalid unique column set" + rawFieldNames + ". Multiple records found with value " + values.toString(), rawFieldNames.toString()); } c.moveToFirst(); byte[] data = c.getBlob(c.getColumnIndexOrThrow(DatabaseHelper.DATA_COL)); c.close(); return newObject(data); } @Override public T getRecordForValue(String rawFieldName, Object value) throws NoSuchElementException, InvalidIndexException { Pair<String, String[]> whereClause = helper.createWhere(new String[]{rawFieldName}, new Object[]{value}, em, t); String scrubbedName = AndroidTableBuilder.scrubName(rawFieldName); Cursor c = helper.getHandle().query(table, new String[]{DatabaseHelper.DATA_COL}, whereClause.first, whereClause.second, null, null, null); if (c.getCount() == 0) { c.close(); throw new NoSuchElementException("No element in table " + table + " with name " + scrubbedName + " and value " + value.toString()); } if (c.getCount() > 1) { c.close(); throw new InvalidIndexException("Invalid unique column " + scrubbedName + ". Multiple records found with value " + value.toString(), scrubbedName); } c.moveToFirst(); byte[] data = c.getBlob(c.getColumnIndexOrThrow(DatabaseHelper.DATA_COL)); c.close(); return newObject(data); } @Override public T newObject(byte[] data, int dbEntryId) { return newObject(data); } public T newObject(byte[] data) { try { T e = ctype.newInstance(); e.readExternal(new DataInputStream(new ByteArrayInputStream(data)), helper.getPrototypeFactory()); return e; } catch (IllegalAccessException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } catch (InstantiationException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (DeserializationException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } @Override public int add(Externalizable e) { SQLiteDatabase db = helper.getHandle(); int i = -1; try { db.beginTransaction(); long ret = db.insertOrThrow(table, DatabaseHelper.DATA_COL, helper.getContentValues(e)); if (ret > Integer.MAX_VALUE) { throw new RuntimeException("Waaaaaaaaaay too many values"); } i = (int)ret; db.setTransactionSuccessful(); } finally { db.endTransaction(); } return i; } @Override public void close() { helper.getHandle().close(); } @Override public boolean exists(int id) { Cursor c = helper.getHandle().query(table, new String[]{DatabaseHelper.ID_COL}, DatabaseHelper.ID_COL + "= ? ", new String[]{String.valueOf(id)}, null, null, null); if (c.getCount() == 0) { c.close(); return false; } if (c.getCount() > 1) { c.close(); throw new InvalidIndexException("Invalid ID column. Multiple records found with value " + id, "ID"); } c.close(); return true; } @Override public net.sqlcipher.database.SQLiteDatabase getAccessLock() { // TODO Auto-generated method stub return null; } @Override public int getNumRecords() { Cursor c = helper.getHandle().query(table, new String[]{DatabaseHelper.ID_COL}, null, null, null, null, null); int records = c.getCount(); c.close(); return records; } @Override public boolean isEmpty() { return getNumRecords() == 0; } @Override public SqlStorageIterator<T> iterate() { Cursor c = helper.getHandle().query(table, new String[]{DatabaseHelper.ID_COL, DatabaseHelper.DATA_COL}, null, null, null, null, DatabaseHelper.ID_COL); return new SqlStorageIterator<>(c, this); } @Override public Iterator<T> iterator() { return iterate(); } @Override public T read(int id) { return newObject(readBytes(id)); } @Override public byte[] readBytes(int id) { Cursor c = helper.getHandle().query(table, new String[]{DatabaseHelper.ID_COL, DatabaseHelper.DATA_COL}, DatabaseHelper.ID_COL + "=?", new String[]{String.valueOf(id)}, null, null, null); c.moveToFirst(); byte[] blob = c.getBlob(c.getColumnIndexOrThrow(DatabaseHelper.DATA_COL)); c.close(); return blob; } @Override public void remove(int id) { SQLiteDatabase db = helper.getHandle(); db.beginTransaction(); try { db.delete(table, DatabaseHelper.ID_COL + "=?", new String[]{String.valueOf(id)}); db.setTransactionSuccessful(); } finally { db.endTransaction(); } } @Override public void remove(List ids) { if (ids.size() == 0) { return; } SQLiteDatabase db = helper.getHandle(); db.beginTransaction(); try { List<Pair<String, String[]>> whereParamList = AndroidTableBuilder.sqlList(ids); for (Pair<String, String[]> whereParams : whereParamList) { int rowsRemoved = db.delete(table, DatabaseHelper.ID_COL + " IN " + whereParams.first, whereParams.second); } db.setTransactionSuccessful(); } finally { db.endTransaction(); } } @Override public void remove(Persistable p) { this.remove(p.getID()); } @Override public void removeAll() { SQLiteDatabase db = helper.getHandle(); db.beginTransaction(); try { db.delete(table, null, null); db.setTransactionSuccessful(); } finally { db.endTransaction(); } } @Override public Vector<Integer> removeAll(EntityFilter ef) { Vector<Integer> removed = new Vector<>(); for (IStorageIterator iterator = this.iterate(); iterator.hasMore(); ) { int id = iterator.nextID(); switch (ef.preFilter(id, null)) { case EntityFilter.PREFILTER_INCLUDE: removed.add(id); continue; case EntityFilter.PREFILTER_EXCLUDE: continue; } if (ef.matches(read(id))) { removed.add(id); } } if (removed.size() == 0) { return removed; } List<Pair<String, String[]>> whereParamList = AndroidTableBuilder.sqlList(removed); SQLiteDatabase db = helper.getHandle(); db.beginTransaction(); try { for (Pair<String, String[]> whereParams : whereParamList) { db.delete(table, DatabaseHelper.ID_COL + " IN " + whereParams.first, whereParams.second); } db.setTransactionSuccessful(); } finally { db.endTransaction(); } return removed; } @Override public void update(int id, Externalizable e) { SQLiteDatabase db = helper.getHandle(); db.beginTransaction(); try { db.update(table, helper.getContentValues(e), DatabaseHelper.ID_COL + "=?", new String[]{String.valueOf(id)}); db.setTransactionSuccessful(); } finally { db.endTransaction(); } } @Override public void write(Persistable p) { if (p.getID() != -1) { update(p.getID(), p); return; } SQLiteDatabase db = helper.getHandle(); try { db.beginTransaction(); long ret = db.insertOrThrow(table, DatabaseHelper.DATA_COL, helper.getContentValues(p)); if (ret > Integer.MAX_VALUE) { throw new RuntimeException("Waaaaaaaaaay too many values"); } int id = (int)ret; //Now we need to put the id into the record p.setID(id); db.update(table, helper.getContentValues(p), DatabaseHelper.ID_COL + "=?", new String[]{String.valueOf(id)}); db.setTransactionSuccessful(); } finally { db.endTransaction(); } } }