package org.commcare.engine.cases;
import net.sqlcipher.database.SQLiteDatabase;
import org.commcare.CommCareApplication;
import org.commcare.cases.ledger.Ledger;
import org.commcare.cases.ledger.LedgerPurgeFilter;
import org.commcare.cases.util.CasePurgeFilter;
import org.commcare.logging.AndroidLogger;
import org.commcare.models.database.SqlStorage;
import org.commcare.android.database.user.models.ACase;
import org.commcare.models.database.user.models.AndroidCaseIndexTable;
import org.commcare.utils.CommCareUtil;
import org.javarosa.core.model.User;
import org.javarosa.core.model.condition.EvaluationContext;
import org.javarosa.core.model.instance.AbstractTreeElement;
import org.javarosa.core.model.instance.DataInstance;
import org.javarosa.core.model.instance.TreeReference;
import org.javarosa.core.services.Logger;
import org.javarosa.core.services.storage.IStorageIterator;
import org.javarosa.model.xform.XPathReference;
import java.util.Vector;
/**
* Utilities for performing complex operations on the case database.
*
* Created by ctsims on 2/25/2016.
*/
public class CaseUtils {
/**
* Perform a case purge against the logged in user with the logged in app in local storage.
*
* Will fail if the app is not ready for DB operations at the user level.
*/
public static void purgeCases() {
long start = System.currentTimeMillis();
//We need to determine if we're using ownership for purging. For right now, only in sync mode
Vector<String> owners = new Vector<>();
Vector<String> users = new Vector<>();
for (IStorageIterator<User> userIterator = CommCareApplication.instance()
.getUserStorage(User.STORAGE_KEY, User.class).iterate(); userIterator.hasMore(); ) {
String id = userIterator.nextRecord().getUniqueId();
owners.addElement(id);
users.addElement(id);
}
//Now add all of the relevant groups
//TODO: Wow. This is.... kind of megasketch
for (String userId : users) {
DataInstance instance = CommCareUtil.loadFixture("user-groups", userId);
if (instance == null) {
continue;
}
EvaluationContext ec = new EvaluationContext(instance);
for (TreeReference ref : ec.expandReference(XPathReference.getPathExpr("/groups/group/@id").getReference())) {
AbstractTreeElement<AbstractTreeElement> idelement = ec.resolveReference(ref);
if (idelement.getValue() != null) {
owners.addElement(idelement.getValue().uncast().getString());
}
}
}
SQLiteDatabase db;
db = CommCareApplication.instance().getUserDbHandle();
db.beginTransaction();
int removedCaseCount;
int removedLedgers;
try {
SqlStorage<ACase> storage = CommCareApplication.instance().getUserStorage(ACase.STORAGE_KEY, ACase.class);
CasePurgeFilter filter = new CasePurgeFilter(storage, owners);
if (filter.invalidEdgesWereRemoved()) {
Logger.log(AndroidLogger.SOFT_ASSERT, "An invalid edge was created in the internal " +
"case DAG of a case purge filter, meaning that at least 1 case on the " +
"device had an index into another case that no longer exists on the device");
Logger.log(AndroidLogger.TYPE_ERROR_ASSERTION, "Case lists on the server and device" +
" were out of sync. The following cases were expected to be on the device, " +
"but were missing: " + filter.getMissingCasesString() + ". As a result, the " +
"following cases were also removed from the device: " + filter.getRemovedCasesString());
}
Vector<Integer> casesRemoved = storage.removeAll(filter);
removedCaseCount = casesRemoved.size();
AndroidCaseIndexTable indexTable = new AndroidCaseIndexTable(db);
for (int recordId : casesRemoved) {
indexTable.clearCaseIndices(recordId);
}
SqlStorage<Ledger> stockStorage = CommCareApplication.instance().getUserStorage(Ledger.STORAGE_KEY, Ledger.class);
LedgerPurgeFilter stockFilter = new LedgerPurgeFilter(stockStorage, storage);
removedLedgers = stockStorage.removeAll(stockFilter).size();
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
long taken = System.currentTimeMillis() - start;
Logger.log(AndroidLogger.TYPE_MAINTENANCE, String.format(
"Purged [%d Case, %d Ledger] records in %dms",
removedCaseCount, removedLedgers, taken));
}
}