Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package org.commcare.android.database.connect.models;

import org.commcare.android.storage.framework.Persisted;
import org.commcare.models.framework.Persisting;
import org.commcare.modern.database.Table;
import org.commcare.modern.models.MetaField;
import org.javarosa.core.services.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
* DB model for personal_id_credential table
*/
@Table(PersonalIdCredential.STORAGE_KEY)
public class PersonalIdCredential extends Persisted implements Serializable {

public static final String STORAGE_KEY = "credential";

public static final String META_APP_NAME = "app_name";
public static final String META_APP_ID = "app_id";
public static final String META_TYPE = "type";
public static final String META_ISSUED_DATE = "issued_date";
public static final String META_TITLE = "title";
public static final String META_CREDENTIAL = "credential";

@Persisting(1)
@MetaField(META_APP_NAME)
private String appName;

@Persisting(2)
@MetaField(META_APP_ID)
private String appId;

@Persisting(3)
@MetaField(META_TYPE)
private String type;

@Persisting(4)
@MetaField(META_ISSUED_DATE)
private String issuedDate;

@Persisting(5)
@MetaField(META_TITLE)
private String title;

@Persisting(6)
@MetaField(META_CREDENTIAL)
private String credential;

// Constructors
public PersonalIdCredential() {
}

public PersonalIdCredential(String appName, String appId, String type,
String issuedDate, String title, String credential) {
this.appName = appName;
this.appId = appId;
this.type = type;
this.issuedDate = issuedDate;
this.title = title;
this.credential = credential;
}

// Getters
public String getAppName() {
return appName;
}

public String getAppId() {
return appId;
}

public String getType() {
return type;
}

public String getIssuedDate() {
return issuedDate;
}

public String getTitle() {
return title;
}

public String getCredential() {
return credential;
}

// Setters
public void setAppName(String appName) {
this.appName = appName;
}

public void setAppId(String setAppId) {
this.appId = setAppId;
}

public void setType(String type) {
this.type = type;
}

public void setIssuedDate(String issuedDate) {
this.issuedDate = issuedDate;
}

public void setTitle(String title) {
this.title = title;
}

public void setCredential(String credential) {
this.credential = credential;
}

/**
* Parses full response string and returns valid & corrupt credentials separately.
*/
public static PersonalIdValidAndCorruptCredential fromJsonArray(JSONArray jsonArray) {
List<PersonalIdCredential> valid = new ArrayList<>();
List<PersonalIdCredential> corrupt = new ArrayList<>();

for (int i = 0; i < jsonArray.length(); i++) {
JSONObject obj = null;
try {
obj = jsonArray.getJSONObject(i);

PersonalIdCredential credential = new PersonalIdCredential();
credential.setAppName(obj.getString(META_APP_NAME));
credential.setAppId(obj.getString(META_APP_ID));
credential.setType(obj.getString(META_TYPE));
credential.setIssuedDate(obj.getString(META_ISSUED_DATE));
credential.setTitle(obj.getString(META_TITLE));
credential.setCredential(obj.getString(META_CREDENTIAL));
valid.add(credential);

} catch (JSONException e) {
Logger.exception("Corrupt PersonalIdCredential at index " + i, e);
if (obj != null) {
corrupt.add(corruptCredentialFromJson(obj));
}
}
}

return new PersonalIdValidAndCorruptCredential(valid, corrupt);
}


/**
* Creates a PersonalIdCredential with optStrings to recover partial corrupt data.
*/
public static PersonalIdCredential corruptCredentialFromJson(JSONObject json) {
PersonalIdCredential credItem = new PersonalIdCredential();
credItem.appName = json.optString(META_APP_NAME,"");
credItem.appId = json.optString(META_APP_ID,"");
credItem.type = json.optString(META_TYPE,"");
credItem.issuedDate = json.optString(META_ISSUED_DATE,"");
credItem.title = json.optString(META_TITLE,"");
credItem.credential = json.optString(META_CREDENTIAL,"");
return credItem;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.commcare.android.database.connect.models;

import java.util.List;

/**
* Wraps the result of parsing Personal ID Credential response.
* Contains valid and corrupt items separately.
*/
public class PersonalIdValidAndCorruptCredential {
private final List<PersonalIdCredential> validCredentials;
private final List<PersonalIdCredential> corruptCredentials;

public PersonalIdValidAndCorruptCredential(List<PersonalIdCredential> valid, List<PersonalIdCredential> corrupt) {
this.validCredentials = valid;
this.corruptCredentials = corrupt;
}

public List<PersonalIdCredential> getValidCredentials() {
return validCredentials;
}

public List<PersonalIdCredential> getCorruptCredentials() {
return corruptCredentials;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.commcare.android.database.connect.models.ConnectUserRecordV13;
import org.commcare.android.database.connect.models.ConnectUserRecordV14;
import org.commcare.android.database.connect.models.ConnectUserRecordV5;
import org.commcare.android.database.connect.models.PersonalIdCredential;
import org.commcare.models.database.ConcreteAndroidDbHelper;
import org.commcare.models.database.DbUtil;
import org.commcare.models.database.SqlStorage;
Expand Down Expand Up @@ -113,6 +114,10 @@ public void upgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
upgradeFourteenFifteen(db);
oldVersion = 15;
}
if (oldVersion == 15) {
upgradeFifteenSixteen(db);
oldVersion = 16;
}
}

private void upgradeOneTwo(SQLiteDatabase db) {
Expand Down Expand Up @@ -584,6 +589,10 @@ private void upgradeFourteenFifteen(SQLiteDatabase db) {
}
}

private void upgradeFifteenSixteen(SQLiteDatabase db) {
addTableForNewModel(db, PersonalIdCredential.STORAGE_KEY, new PersonalIdCredential());
}
Comment on lines +592 to +594
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider explicit logging / idempotency guard

addTableForNewModel is wrapped in a transaction, but if the upgrade is accidentally re-run (for example after a partially-applied migration) the CREATE TABLE will throw.
Adding a IF NOT EXISTS in TableBuilder#getTableCreateString() or catching the specific exception here would make the upgrade idempotent and safer on retry.

🤖 Prompt for AI Agents
In app/src/org/commcare/models/database/connect/ConnectDatabaseUpgrader.java
around lines 592 to 594, the upgrade method calls addTableForNewModel which
attempts to create a table but does not handle the case where the table already
exists, causing an exception if the upgrade is retried. To fix this, modify the
table creation logic in TableBuilder#getTableCreateString() to include "IF NOT
EXISTS" in the CREATE TABLE statement or catch the specific exception thrown
when the table exists in upgradeFifteenSixteen and handle it gracefully to make
the upgrade idempotent and safe to rerun.


private static void addTableForNewModel(SQLiteDatabase db, String storageKey,
Persistable modelToAdd) {
db.beginTransaction();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.commcare.android.database.connect.models.ConnectMessagingMessageRecord;
import org.commcare.android.database.connect.models.ConnectPaymentUnitRecord;
import org.commcare.android.database.connect.models.ConnectUserRecord;
import org.commcare.android.database.connect.models.PersonalIdCredential;
import org.commcare.logging.DataChangeLog;
import org.commcare.logging.DataChangeLogger;
import org.commcare.models.database.DbUtil;
Expand Down Expand Up @@ -53,8 +54,9 @@ public class DatabaseConnectOpenHelper extends SQLiteOpenHelper {
* V.12 - Added ConnectMessagingChannelRecord table and ConnectMessagingMessageRecord table
* V.13 - Added ConnectJobDeliveryFlagRecord table
* V.14 - Added a photo and isDemo field to ConnectUserRecord
* V.16 - Added personal_id_credential table
*/
private static final int CONNECT_DB_VERSION = 15;
private static final int CONNECT_DB_VERSION = 16;

private static final String CONNECT_DB_LOCATOR = "database_connect";

Expand Down Expand Up @@ -130,6 +132,9 @@ public void onCreate(SQLiteDatabase database) {
builder = new TableBuilder(ConnectJobDeliveryFlagRecord.class);
database.execSQL(builder.getTableCreateString());

builder = new TableBuilder(PersonalIdCredential.class);
database.execSQL(builder.getTableCreateString());

DbUtil.createNumbersTable(database);

database.setVersion(CONNECT_DB_VERSION);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,9 @@ public class FormStorageTest {
, "org.commcare.android.database.global.models.GlobalErrorRecord"

,"org.commcare.android.database.connect.models.ConnectUserRecordV14"

//Added in 2.58
,"org.commcare.android.database.connect.models.PersonalIdCredential"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add a comment above this line saying //Added in 2.58 similar to other version comments above

);


Expand Down
Loading