Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web Apps: Lazy loads case list #1388

Merged
merged 5 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/cli/java/org/commcare/util/screen/EntityScreenHelper.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package org.commcare.util.screen;

import org.commcare.cases.entity.AsyncEntity;
import org.commcare.cases.entity.AsyncNodeEntityFactory;
import org.commcare.cases.entity.Entity;
import org.commcare.cases.entity.EntitySortNotificationInterface;
import org.commcare.cases.entity.EntitySorter;
import org.commcare.cases.entity.EntityStorageCache;
import org.commcare.cases.entity.EntityStringFilterer;
import org.commcare.cases.entity.NodeEntityFactory;
import org.commcare.suite.model.Detail;
Expand All @@ -13,6 +16,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

Expand All @@ -32,7 +36,12 @@ public class EntityScreenHelper {
*/
public static List<Entity<TreeReference>> initEntities(EvaluationContext context, Detail detail,
EntityScreenContext entityScreenContext, TreeReference[] entitiesRefs) {
NodeEntityFactory nodeEntityFactory = new NodeEntityFactory(detail, context);
NodeEntityFactory nodeEntityFactory;
if (detail.isLazyLoading()) {
nodeEntityFactory = new AsyncNodeEntityFactory(detail, context, null);
} else {
nodeEntityFactory = new NodeEntityFactory(detail, context);
}
List<Entity<TreeReference>> entities = new ArrayList<>();
for (TreeReference reference : entitiesRefs) {
entities.add(nodeEntityFactory.getEntity(reference));
Expand Down
96 changes: 61 additions & 35 deletions src/main/java/org/commcare/cases/entity/AsyncEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import org.commcare.cases.util.StringUtils;
import org.commcare.suite.model.DetailField;
import org.commcare.suite.model.DetailGroup;
import org.commcare.suite.model.Text;
import org.javarosa.core.model.condition.EvaluationContext;
import org.javarosa.core.model.instance.TreeReference;
Expand All @@ -15,6 +16,8 @@
import java.util.Enumeration;
import java.util.Hashtable;

import javax.annotation.Nullable;

/**
* An AsyncEntity is an entity reference which is capable of building its
* values (evaluating all Text elements/background data elements) lazily
Expand All @@ -38,10 +41,12 @@ public class AsyncEntity extends Entity<TreeReference> {
private final String[][] sortDataPieces;
private final EvaluationContext context;
private final Hashtable<String, XPathExpression> mVariableDeclarations;
private final DetailGroup mDetailGroup;
private boolean mVariableContextLoaded = false;
private final String mCacheIndex;
private final String mDetailId;

@Nullable
private final EntityStorageCache mEntityStorageCache;

/*
Expand All @@ -59,8 +64,8 @@ public class AsyncEntity extends Entity<TreeReference> {

public AsyncEntity(DetailField[] fields, EvaluationContext ec,
TreeReference t, Hashtable<String, XPathExpression> variables,
EntityStorageCache cache, String cacheIndex, String detailId,
String extraKey) {
@Nullable EntityStorageCache cache, String cacheIndex, String detailId,
String extraKey, DetailGroup detailGroup) {
super(t, extraKey);

this.fields = fields;
Expand All @@ -77,6 +82,7 @@ public AsyncEntity(DetailField[] fields, EvaluationContext ec,
this.mCacheIndex = cacheIndex;

this.mDetailId = detailId;
this.mDetailGroup = detailGroup;
}

private void loadVariableContext() {
Expand Down Expand Up @@ -121,47 +127,55 @@ public String getNormalizedField(int i) {

@Override
public String getSortField(int i) {
if (mEntityStorageCache.lockCache()) {
//get our second lock.
synchronized (mAsyncLock) {
if (sortData[i] == null) {
// sort data not in search field cache; load and store it
Text sortText = fields[i].getSort();
if (sortText == null) {
mEntityStorageCache.releaseCache();
return null;
}
try {
if (mEntityStorageCache == null || mEntityStorageCache.lockCache()) {
//get our second lock.
synchronized (mAsyncLock) {
if (sortData[i] == null) {
// sort data not in search field cache; load and store it
Text sortText = fields[i].getSort();
if (sortText == null) {
return null;
}

String cacheKey = mEntityStorageCache.getCacheKey(mDetailId, String.valueOf(i));
String cacheKey = null;
if (mEntityStorageCache != null) {
cacheKey = mEntityStorageCache.getCacheKey(mDetailId, String.valueOf(i));

if (mCacheIndex != null) {
//Check the cache!
String value = mEntityStorageCache.retrieveCacheValue(mCacheIndex, cacheKey);
if (value != null) {
this.setSortData(i, value);
mEntityStorageCache.releaseCache();
return sortData[i];
if (mCacheIndex != null) {
//Check the cache!
String value = mEntityStorageCache.retrieveCacheValue(mCacheIndex, cacheKey);
if (value != null) {
this.setSortData(i, value);
return sortData[i];
}
}
}
}

loadVariableContext();
try {
sortText = fields[i].getSort();
if (sortText == null) {
this.setSortData(i, getFieldString(i));
} else {
this.setSortData(i, StringUtils.normalize(sortText.evaluate(context)));
loadVariableContext();
try {
sortText = fields[i].getSort();
if (sortText == null) {
this.setSortData(i, getFieldString(i));
} else {
this.setSortData(i, StringUtils.normalize(sortText.evaluate(context)));
}
if (mEntityStorageCache != null) {
mEntityStorageCache.cache(mCacheIndex, cacheKey, sortData[i]);
}
} catch (XPathException xpe) {
Logger.exception("Error while evaluating sort field", xpe);
xpe.printStackTrace();
sortData[i] = "<invalid xpath: " + xpe.getMessage() + ">";
}

mEntityStorageCache.cache(mCacheIndex, cacheKey, sortData[i]);
} catch (XPathException xpe) {
Logger.exception("Error while evaluating sort field", xpe);
xpe.printStackTrace();
sortData[i] = "<invalid xpath: " + xpe.getMessage() + ">";
}

return sortData[i];
}
}
} finally {
if (mEntityStorageCache != null) {
mEntityStorageCache.releaseCache();
return sortData[i];
}
}
return null;
Expand Down Expand Up @@ -218,6 +232,9 @@ private void setSortData(int i, String val) {
}

public void setSortData(String cacheKey, String val) {
if (mEntityStorageCache == null) {
throw new IllegalStateException("No entity cache defined");
}
int sortIndex = mEntityStorageCache.getSortFieldIdFromCacheKey(mDetailId, cacheKey);
if (sortIndex != -1) {
setSortData(sortIndex, val);
Expand All @@ -233,4 +250,13 @@ private static String[] breakUpField(String input) {
return input.split("\\s+");
}
}

@Nullable
@Override
public String getGroupKey() {
if (mDetailGroup != null) {
return (String)mDetailGroup.getFunction().eval(context);
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import java.util.Hashtable;
import java.util.List;

import javax.annotation.Nullable;

/**
* @author ctsims
*/
Expand All @@ -19,6 +21,7 @@ public class AsyncNodeEntityFactory extends NodeEntityFactory {
private final OrderedHashtable<String, XPathExpression> mVariableDeclarations;

private final Hashtable<String, AsyncEntity> mEntitySet = new Hashtable<>();
@Nullable
private final EntityStorageCache mEntityCache;

private CacheHost mCacheHost = null;
Expand All @@ -29,7 +32,8 @@ public class AsyncNodeEntityFactory extends NodeEntityFactory {
// Don't show entity list until we primeCache and caches all fields
private final boolean isBlockingAsyncMode;

public AsyncNodeEntityFactory(Detail d, EvaluationContext ec, EntityStorageCache entityStorageCache) {
public AsyncNodeEntityFactory(Detail d, EvaluationContext ec,
@Nullable EntityStorageCache entityStorageCache) {
super(d, ec);

mVariableDeclarations = detail.getVariableDeclarations();
Expand All @@ -54,7 +58,7 @@ public Entity<TreeReference> getEntity(TreeReference data) {
String entityKey = loadCalloutDataMapKey(nodeContext);
AsyncEntity entity =
new AsyncEntity(detail.getFields(), nodeContext, data, mVariableDeclarations,
mEntityCache, mCacheIndex, detail.getId(), entityKey);
mEntityCache, mCacheIndex, detail.getId(), entityKey, detail.getGroup());

if (mCacheIndex != null) {
mEntitySet.put(mCacheIndex, entity);
Expand All @@ -77,7 +81,7 @@ protected void setEvaluationContextDefaultQuerySet(EvaluationContext ec,
* Note that the cache is lazily built upon first case list search.
*/
private void primeCache() {
if (mTemplateIsCachable == null || !mTemplateIsCachable || mCacheHost == null) {
if (mEntityCache == null || mTemplateIsCachable == null || !mTemplateIsCachable || mCacheHost == null) {
return;
}

Expand Down
13 changes: 12 additions & 1 deletion src/main/java/org/commcare/suite/model/Detail.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ public class Detail implements Externalizable {
// equal to its width, rather than being computed independently
private boolean useUniformUnitsInCaseTile;

// Loads detail fields lazily when required
private boolean lazyLoading;

private DetailGroup group;

// ENDREGION
Expand All @@ -119,7 +122,8 @@ public Detail(String id, DisplayUnit title, Text noItemsText, String nodeset, Ve
Vector<DetailField> fieldsVector, OrderedHashtable<String, String> variables,
Vector<Action> actions, Callout callout, String fitAcross,
String uniformUnitsString, String forceLandscape, String focusFunction,
String printPathProvided, String relevancy, Global global, DetailGroup group) {
String printPathProvided, String relevancy, Global global, DetailGroup group,
boolean lazyLoading) {

if (detailsVector.size() > 0 && fieldsVector.size() > 0) {
throw new IllegalArgumentException("A detail may contain either sub-details or fields, but not both.");
Expand Down Expand Up @@ -169,6 +173,7 @@ public Detail(String id, DisplayUnit title, Text noItemsText, String nodeset, Ve
}
this.global = global;
this.group = group;
this.lazyLoading = lazyLoading;
}

/**
Expand Down Expand Up @@ -211,6 +216,10 @@ public Detail[] getDetails() {
return details;
}

public boolean isLazyLoading() {
return lazyLoading;
}

/**
* Given a detail, return an array of details that will contain either
* - all child details
Expand Down Expand Up @@ -274,6 +283,7 @@ public void readExternal(DataInputStream in, PrototypeFactory pf) throws IOExcep
parsedRelevancyExpression = (XPathExpression)ExtUtil.read(in, new ExtWrapNullable(new ExtWrapTagged()), pf);
global = (Global)ExtUtil.read(in, new ExtWrapNullable(new ExtWrapTagged()), pf);
group = (DetailGroup) ExtUtil.read(in, new ExtWrapNullable(DetailGroup.class), pf);
lazyLoading = ExtUtil.readBool(in);
}

@Override
Expand All @@ -296,6 +306,7 @@ public void writeExternal(DataOutputStream out) throws IOException {
parsedRelevancyExpression == null ? null : new ExtWrapTagged(parsedRelevancyExpression)));
ExtUtil.write(out, new ExtWrapNullable(global == null ? null : new ExtWrapTagged(global)));
ExtUtil.write(out, new ExtWrapNullable(group));
ExtUtil.writeBool(out, lazyLoading);
}

public OrderedHashtable<String, XPathExpression> getVariableDeclarations() {
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/org/commcare/xml/DetailParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public Detail parse() throws InvalidStructureException, IOException, XmlPullPars
String forceLandscapeView = parser.getAttributeValue(null, "force-landscape");
String printTemplatePath = parser.getAttributeValue(null, "print-template");
String relevancy = parser.getAttributeValue(null, "relevant");
boolean isLazyLoading = Boolean.parseBoolean(parser.getAttributeValue(null, "lazy_loading"));

// First fetch the title
getNextTagInBlock("detail");
Expand Down Expand Up @@ -131,7 +132,7 @@ public Detail parse() throws InvalidStructureException, IOException, XmlPullPars

return new Detail(id, title, noItemsText, nodeset, subdetails, fields, variables, actions, callout,
fitAcross, useUniformUnits, forceLandscapeView, focusFunction, printTemplatePath,
relevancy, global, detailGroup);
relevancy, global, detailGroup, isLazyLoading);
}

protected DetailParser getDetailParser() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,12 @@ public void testDetailWithBorder() {
assertTrue(field1.getShowBorder());
}

@Test
public void testDetailWithLazyLoadingSet() {
Detail detail = mApp.getSession().getPlatform().getDetail("m0_case_short");
assertTrue(detail.isLazyLoading());
}

@Test
public void testDefaultEndpointRelevancy_shouldBeTrue() {
Endpoint endpoint = mApp.getSession().getPlatform().getEndpoint("endpoint_with_no_relevancy");
Expand Down
2 changes: 1 addition & 1 deletion src/test/resources/app_structure/suite.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</resource>
</locale>

<detail id="m0_case_short">
<detail id="m0_case_short" lazy_loading="true">
<title>
<text>Case List</text>
</title>
Expand Down