Skip to content

Commit

Permalink
Merge pull request #2017 from dimagi/resourceMigrationFix
Browse files Browse the repository at this point in the history
Migration fix for Upgrade and Recovery Table
  • Loading branch information
shubham1g5 authored Jul 9, 2018
2 parents e87ccbe + be52665 commit 521fa13
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 46 deletions.
155 changes: 113 additions & 42 deletions app/src/org/commcare/models/database/app/AppDatabaseUpgrader.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.commcare.modern.database.TableBuilder;
import org.commcare.provider.FormsProviderAPI;
import org.commcare.resources.model.Resource;
import org.commcare.util.LogTypes;
import org.javarosa.core.services.Logger;
import org.javarosa.core.util.externalizable.PrototypeFactory;

Expand All @@ -33,6 +34,10 @@
import java.util.Map;
import java.util.Vector;

import static org.commcare.utils.AndroidCommCarePlatform.GLOBAL_RESOURCE_TABLE_NAME;
import static org.commcare.utils.AndroidCommCarePlatform.RECOVERY_RESOURCE_TABLE_NAME;
import static org.commcare.utils.AndroidCommCarePlatform.UPGRADE_RESOURCE_TABLE_NAME;

/**
* @author ctsims
*/
Expand Down Expand Up @@ -86,8 +91,14 @@ public void upgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}

if (oldVersion == 8) {
if (upgradeEightNine(db)) {
oldVersion = 9;
if (upgradeEightTen(db)) {
oldVersion = 10;
}
}

if (oldVersion == 9) {
if (upgradeNineTen(db)) {
oldVersion = 10;
}
}
//NOTE: If metadata changes are made to the Resource model, they need to be
Expand Down Expand Up @@ -216,11 +227,13 @@ private boolean upgradeSevenEight(SQLiteDatabase db) {
}

// Migrate records form FormProvider and InstanceProvider to new FormDefRecord and FormRecord respectively
private boolean upgradeEightNine(SQLiteDatabase db) {
private boolean upgradeEightTen(SQLiteDatabase db) {
boolean success;
db.beginTransaction();
try {
upgradeXFormAndroidInstallerV1(db);
upgradeXFormAndroidInstallerV1(GLOBAL_RESOURCE_TABLE_NAME, db);
upgradeXFormAndroidInstallerV1(UPGRADE_RESOURCE_TABLE_NAME, db);
upgradeXFormAndroidInstallerV1(RECOVERY_RESOURCE_TABLE_NAME, db);

// Create FormDef table
TableBuilder builder = new TableBuilder(FormDefRecord.class);
Expand All @@ -233,6 +246,7 @@ private boolean upgradeEightNine(SQLiteDatabase db) {
db.endTransaction();
}


// Delete entries from FormsProvider if migration has been successful
if (success) {
try {
Expand All @@ -246,6 +260,19 @@ private boolean upgradeEightNine(SQLiteDatabase db) {
return success;
}

// I only exist since there was a time when there were no Upgrade and Recovery table in v8-v9 migration
private boolean upgradeNineTen(SQLiteDatabase db) {
db.beginTransaction();
try {
upgradeToResourcesV10(UPGRADE_RESOURCE_TABLE_NAME, db);
upgradeToResourcesV10(RECOVERY_RESOURCE_TABLE_NAME, db);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return true;
}

// migrate formProvider entries to db
private void migrateFormProvider(SQLiteDatabase db) {
Cursor cursor = null;
Expand All @@ -272,48 +299,92 @@ private void safeCloseCursor(Cursor cursor) {
}
}

private void upgradeXFormAndroidInstallerV1(SQLiteDatabase db) {
// Get Global Resource Storage using AndroidPrototypeFactoryV1
SqlStorage<Resource> oldGlobalResourceStorage = new SqlStorage<>(
"GLOBAL_RESOURCE_TABLE",
Resource.class,
new ConcreteAndroidDbHelper(context, db) {
@Override
public PrototypeFactory getPrototypeFactory() {
return AndroidPrototypeFactoryV1.getAndroidPrototypeFactoryV1(c);
}
});

Vector<Resource> updateResourceList = new Vector<>();

// If Resource Installer is of Type XFormAndroidInstallerV1 , update it to XFormAndroidInstaller
// and add resource record to the updateResourceList
for (Resource resource : oldGlobalResourceStorage) {
if (resource.getInstaller() instanceof XFormAndroidInstallerV1) {
XFormAndroidInstallerV1 oldInstaller = (XFormAndroidInstallerV1)resource.getInstaller();
String contentUri = oldInstaller.getContentUri();
int formDefId = -1;
if (!StringUtils.isEmpty(contentUri)) {
formDefId = Integer.valueOf(Uri.parse(contentUri).getLastPathSegment());
/**
* There can be 2 different configurations for resource table here
* 1. Either the v8-v9 upgrade failed and rsources are in v8 state
* 2. v8-v9 upgrade was successful and resources are already in v9/v10 state (Resources downloaded after the update)
*
* So we wanna assume 1 and try updating the resources to v10,
* if above fails, we are checking for 2 i.e if resources are in v10 state.
* If they are not we are going to wipe the table
*
* @param tableName Resource table that need to be upgraded
* @param db App DB
*/
private void upgradeToResourcesV10(String tableName, SQLiteDatabase db) {
// Safe checking against calling this method by mistake for Global table
if (tableName.contentEquals(GLOBAL_RESOURCE_TABLE_NAME)) {
return;
}

try {
upgradeXFormAndroidInstallerV1(tableName, db);
} catch (Exception e) {
try {
SqlStorage<Resource> newResourceStorage = new SqlStorage<>(
tableName,
Resource.class,
new ConcreteAndroidDbHelper(context, db));
for (Resource resource : newResourceStorage) {
// Do nothing, just checking if we can read all resources successfully
// signifying that they are already following new v10 model
}
XFormAndroidInstaller newInstaller = new XFormAndroidInstaller(
oldInstaller.getLocalLocation(),
oldInstaller.getLocalDestination(),
oldInstaller.getUpgradeDestination(),
oldInstaller.getNamespace(),
formDefId);
resource.setInstaller(newInstaller);
updateResourceList.add(resource);
} catch (Exception ex) {
SqlStorage.wipeTable(db, tableName);
Logger.log(LogTypes.SOFT_ASSERT, "Wiped table on upgrade " + tableName);
}
}
}

// Rewrite the records in updateResourceList using the standard AndroidProtoTypeFactory
SqlStorage<Resource> newGlobalResourceStorage = new SqlStorage<>(
"GLOBAL_RESOURCE_TABLE",
Resource.class,
new ConcreteAndroidDbHelper(context, db));
for (Resource resource : updateResourceList) {
newGlobalResourceStorage.update(resource.getID(), resource);

private void upgradeXFormAndroidInstallerV1(String tableName, SQLiteDatabase db) {
db.beginTransaction();
try {
// Get Global Resource Storage using AndroidPrototypeFactoryV1
SqlStorage<Resource> oldResourceStorage = new SqlStorage<>(
tableName,
Resource.class,
new ConcreteAndroidDbHelper(context, db) {
@Override
public PrototypeFactory getPrototypeFactory() {
return AndroidPrototypeFactoryV1.getAndroidPrototypeFactoryV1(c);
}
});

Vector<Resource> updateResourceList = new Vector<>();

// If Resource Installer is of Type XFormAndroidInstallerV1 , update it to XFormAndroidInstaller
// and add resource record to the updateResourceList
for (Resource resource : oldResourceStorage) {
if (resource.getInstaller() instanceof XFormAndroidInstallerV1) {
XFormAndroidInstallerV1 oldInstaller = (XFormAndroidInstallerV1)resource.getInstaller();
String contentUri = oldInstaller.getContentUri();
int formDefId = -1;
if (!StringUtils.isEmpty(contentUri)) {
formDefId = Integer.valueOf(Uri.parse(contentUri).getLastPathSegment());
}
XFormAndroidInstaller newInstaller = new XFormAndroidInstaller(
oldInstaller.getLocalLocation(),
oldInstaller.getLocalDestination(),
oldInstaller.getUpgradeDestination(),
oldInstaller.getNamespace(),
formDefId);
resource.setInstaller(newInstaller);
updateResourceList.add(resource);
}
}

// Rewrite the records in updateResourceList using the standard AndroidProtoTypeFactory
SqlStorage<Resource> newResourceStorage = new SqlStorage<>(
tableName,
Resource.class,
new ConcreteAndroidDbHelper(context, db));
for (Resource resource : updateResourceList) {
newResourceStorage.update(resource.getID(), resource);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.commcare.models.database.app;

import android.content.Context;
import android.util.Log;

import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteException;
Expand Down Expand Up @@ -34,8 +35,9 @@ public class DatabaseAppOpenHelper extends SQLiteOpenHelper {
* V.7 - Update serialized fixtures in db to use new schema
* V.8 - Add fields to UserKeyRecord to support PIN auth
* V.9 - Adds FormRecord and Instance Record tables, XFormAndroidInstaller: contentUri -> formDefId
* V.10 - No Change, Added because of incomplete resource table migration for v8 to v9
*/
private static final int DB_VERSION_APP = 9;
private static final int DB_VERSION_APP = 10;

private static final String DB_LOCATOR_PREF_APP = "database_app_";

Expand Down
10 changes: 7 additions & 3 deletions app/src/org/commcare/utils/AndroidCommCarePlatform.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
*/
public class AndroidCommCarePlatform extends CommCarePlatform {

public static final String GLOBAL_RESOURCE_TABLE_NAME = "GLOBAL_RESOURCE_TABLE";
public static final String UPGRADE_RESOURCE_TABLE_NAME = "UPGRADE_RESOURCE_TABLE";
public static final String RECOVERY_RESOURCE_TABLE_NAME = "RECOVERY_RESOURCE_TABLE";

private final Hashtable<String, Integer> xmlnstable;
private ResourceTable global;
private ResourceTable upgrade;
Expand Down Expand Up @@ -59,21 +63,21 @@ public int getFormDefId(String xFormNamespace) {

public ResourceTable getGlobalResourceTable() {
if (global == null) {
global = new AndroidResourceTable(app.getStorage("GLOBAL_RESOURCE_TABLE", Resource.class), new AndroidResourceInstallerFactory());
global = new AndroidResourceTable(app.getStorage(GLOBAL_RESOURCE_TABLE_NAME, Resource.class), new AndroidResourceInstallerFactory());
}
return global;
}

public ResourceTable getUpgradeResourceTable() {
if (upgrade == null) {
upgrade = new AndroidResourceTable(app.getStorage("UPGRADE_RESOURCE_TABLE", Resource.class), new AndroidResourceInstallerFactory());
upgrade = new AndroidResourceTable(app.getStorage(UPGRADE_RESOURCE_TABLE_NAME, Resource.class), new AndroidResourceInstallerFactory());
}
return upgrade;
}

public ResourceTable getRecoveryTable() {
if (recovery == null) {
recovery = new AndroidResourceTable(app.getStorage("RECOVERY_RESOURCE_TABLE", Resource.class), new AndroidResourceInstallerFactory());
recovery = new AndroidResourceTable(app.getStorage(RECOVERY_RESOURCE_TABLE_NAME, Resource.class), new AndroidResourceInstallerFactory());
}
return recovery;
}
Expand Down

0 comments on commit 521fa13

Please sign in to comment.