Skip to content

Commit

Permalink
Merge pull request #748 from seadowg/entity-additions
Browse files Browse the repository at this point in the history
Additions needed for offline entity creation
  • Loading branch information
lognaturel authored Feb 27, 2024
2 parents b48f5b4 + 94a5efe commit 312ff3c
Show file tree
Hide file tree
Showing 13 changed files with 135 additions and 38 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ if (!project.hasProperty("android")) {
// Be sure to update version in pom.xml to match
// snapshot release = x.x.x-SNAPSHOT
// production release = x.x.x
archiveVersion = '4.3.1'
archiveVersion = '4.4.0-SNAPSHOT'
archiveBaseName = "javarosa"

manifest {
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<!-- Be sure to update version in build.gradle to match -->
<!-- snapshot release = x.x.x-SNAPSHOT -->
<!-- production release = x.x.x -->
<version>4.3.1</version>
<version>4.4.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>javarosa</name>
<description>A Java library for rendering forms that are compliant with ODK XForms spec</description>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
package org.javarosa.benchmarks;

import static org.javarosa.benchmarks.BenchmarkUtils.dryRun;
import static org.javarosa.benchmarks.BenchmarkUtils.prepareAssets;
import static org.javarosa.core.reference.ReferenceManagerTestUtils.setUpSimpleReferenceManager;

import java.io.IOException;
import java.nio.file.Path;

import org.javarosa.core.model.instance.ExternalDataInstance;
import org.javarosa.core.reference.InvalidReferenceException;
import org.javarosa.xml.util.InvalidStructureException;
Expand All @@ -19,6 +12,13 @@
import org.openjdk.jmh.infra.Blackhole;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.nio.file.Path;

import static org.javarosa.benchmarks.BenchmarkUtils.dryRun;
import static org.javarosa.benchmarks.BenchmarkUtils.prepareAssets;
import static org.javarosa.core.reference.ReferenceManagerTestUtils.setUpSimpleReferenceManager;

public class ExternalDataInstanceBuildBenchmark {
public static void main(String[] args) {
dryRun(ExternalDataInstanceBuildBenchmark.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package org.javarosa.core.model.instance;

import org.javarosa.core.model.instance.geojson.GeoJsonExternalInstance;
import org.javarosa.core.reference.InvalidReferenceException;
import org.javarosa.core.reference.ReferenceManager;
import org.javarosa.core.util.externalizable.DeserializationException;
import org.javarosa.core.util.externalizable.ExtUtil;
import org.javarosa.core.util.externalizable.PrototypeFactory;
import org.javarosa.xform.parse.XFormParser;
import org.javarosa.xform.util.XFormUtils;
import org.javarosa.xml.TreeElementParser;
import org.javarosa.xml.util.InvalidStructureException;
import org.javarosa.xml.util.UnfullfilledRequirementsException;
Expand Down Expand Up @@ -46,7 +46,7 @@ private ExternalDataInstance(TreeElement root, String instanceId, String path) {
/**
* Builds an ExternalDataInstance by parsing the file at the given location or fills in a placeholder if the file
* can't be found or derived.
*
* <p>
* The placeholder makes it possible to successfully parse a form without an external
* instance in cases where a client isn't providing a form-filling interface (e.g. form validation or discovery).
*
Expand All @@ -72,15 +72,13 @@ public static ExternalDataInstance build(String instanceSrc, String instanceId)
logger.info("External instance not found, falling back to placeholder");
root = PLACEHOLDER_ROOT;
}

return new ExternalDataInstance(root, instanceId, instanceSrc);
}

private static TreeElement parseExternalInstance(String instanceSrc, String instanceId)
throws IOException, InvalidReferenceException, InvalidStructureException, XmlPullParserException, UnfullfilledRequirementsException {
String path = getPath(instanceSrc);
return instanceSrc.contains("file-csv") ? CsvExternalInstance.parse(instanceId, path)
: instanceSrc.endsWith("geojson") ? GeoJsonExternalInstance.parse(instanceId, path)
: XmlExternalInstance.parse(instanceId, path);
return XFormUtils.getExternalInstance(ReferenceManager.instance(), instanceId, instanceSrc);
}

@Override
Expand Down Expand Up @@ -117,7 +115,8 @@ public void readExternal(DataInputStream in, PrototypeFactory pf)
path = ExtUtil.readString(in);
try {
setRoot(parseExternalInstance(path, getInstanceId()));
} catch (InvalidReferenceException | InvalidStructureException | XmlPullParserException | UnfullfilledRequirementsException e) {
} catch (InvalidReferenceException | InvalidStructureException | XmlPullParserException |
UnfullfilledRequirementsException e) {
throw new DeserializationException("Unable to parse external instance: " + e);
}
}
Expand All @@ -127,14 +126,4 @@ public void writeExternal(DataOutputStream out) throws IOException {
super.writeExternal(out);
ExtUtil.write(out, path);
}

/**
* Returns the path of the URI at srcLocation.
*
* @param srcLocation the value of the <code>src</code> attribute of the <code>instance</code> element
*/
private static String getPath(String srcLocation) throws InvalidReferenceException {
String uri = ReferenceManager.instance().deriveReference(srcLocation).getLocalURI();
return uri.startsWith("//") /* todo why is this? */ ? uri.substring(1) : uri;
}
}
}
6 changes: 5 additions & 1 deletion src/main/java/org/javarosa/entities/Entity.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ public class Entity {

public final String dataset;
public final List<Pair<String, String>> properties;
public final String id;
public final String label;

public Entity(String dataset, List<Pair<String, String>> properties) {
public Entity(String dataset, String id, String label, List<Pair<String, String>> properties) {
this.dataset = dataset;
this.id = id;
this.label = label;
this.properties = properties;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
import org.javarosa.core.model.IDataReference;
import org.javarosa.core.model.data.IAnswerData;
import org.javarosa.core.model.instance.FormInstance;
import org.javarosa.core.model.instance.TreeElement;
import org.javarosa.entities.internal.Entities;
import org.javarosa.entities.internal.EntityFormParser;
import org.javarosa.entities.internal.EntityFormExtra;
import org.javarosa.form.api.FormEntryModel;
import org.javarosa.entities.internal.EntityFormParser;
import org.javarosa.form.api.FormEntryFinalizationProcessor;
import org.javarosa.form.api.FormEntryModel;
import org.javarosa.model.xform.XPathReference;

import java.util.List;
Expand All @@ -28,7 +29,8 @@ public void processForm(FormEntryModel formEntryModel) {
EntityFormExtra entityFormExtra = formDef.getExtras().get(EntityFormExtra.class);
List<Pair<XPathReference, String>> saveTos = entityFormExtra.getSaveTos();

String dataset = EntityFormParser.parseFirstDatasetToCreate(mainInstance);
TreeElement entityElement = EntityFormParser.getEntityElement(mainInstance);
String dataset = EntityFormParser.parseFirstDatasetToCreate(entityElement);
if (dataset != null) {
List<Pair<String, String>> fields = saveTos.stream().map(saveTo -> {
IDataReference reference = saveTo.getFirst();
Expand All @@ -41,7 +43,9 @@ public void processForm(FormEntryModel formEntryModel) {
}
}).collect(Collectors.toList());

formEntryModel.getExtras().put(new Entities(asList(new Entity(dataset, fields))));
String id = EntityFormParser.parseId(entityElement);
String label = EntityFormParser.parseLabel(entityElement);
formEntryModel.getExtras().put(new Entities(asList(new Entity(dataset, id, label, fields))));
} else {
formEntryModel.getExtras().put(new Entities(emptyList()));
}
Expand Down
18 changes: 15 additions & 3 deletions src/main/java/org/javarosa/entities/internal/EntityFormParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ private EntityFormParser() {
}

@Nullable
public static String parseFirstDatasetToCreate(FormInstance mainInstance) {
TreeElement entity = getEntityElement(mainInstance);

public static String parseFirstDatasetToCreate(TreeElement entity) {
if (entity != null) {
String create = entity.getAttributeValue(null, "create");

Expand All @@ -30,6 +28,20 @@ public static String parseFirstDatasetToCreate(FormInstance mainInstance) {
return null;
}

public static String parseLabel(TreeElement entity) {
TreeElement labelElement = entity.getFirstChild("label");

if (labelElement != null) {
return (String) labelElement.getValue().getValue();
} else {
return "";
}
}

public static String parseId(TreeElement entity) {
return entity.getAttributeValue("", "id");
}

@Nullable
public static TreeElement getEntityElement(FormInstance mainInstance) {
TreeElement root = mainInstance.getRoot();
Expand Down
57 changes: 57 additions & 0 deletions src/main/java/org/javarosa/xform/parse/ExternalInstanceParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.javarosa.xform.parse;

import org.javarosa.core.model.instance.CsvExternalInstance;
import org.javarosa.core.model.instance.TreeElement;
import org.javarosa.core.model.instance.XmlExternalInstance;
import org.javarosa.core.model.instance.geojson.GeoJsonExternalInstance;
import org.javarosa.core.reference.InvalidReferenceException;
import org.javarosa.core.reference.ReferenceManager;
import org.javarosa.xml.util.InvalidStructureException;
import org.javarosa.xml.util.UnfullfilledRequirementsException;
import org.jetbrains.annotations.NotNull;
import org.xmlpull.v1.XmlPullParserException;

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

public class ExternalInstanceParser {

private List<ExternalDataInstanceProcessor> externalDataInstanceProcessors = new ArrayList<>();

public TreeElement parse(ReferenceManager referenceManager, String instanceId, String instanceSrc) throws IOException, UnfullfilledRequirementsException, InvalidStructureException, XmlPullParserException, InvalidReferenceException {
String path = getPath(referenceManager, instanceSrc);
TreeElement root = instanceSrc.contains("file-csv") ? CsvExternalInstance.parse(instanceId, path)
: instanceSrc.endsWith("geojson") ? GeoJsonExternalInstance.parse(instanceId, path)
: XmlExternalInstance.parse(instanceId, path);

for (ExternalDataInstanceProcessor processor : externalDataInstanceProcessors) {
processor.processInstance(instanceId, root);
}

return root;
}

public void addProcessor(Processor processor) {
externalDataInstanceProcessors.add((ExternalDataInstanceProcessor) processor);
}

/**
* Returns the path of the URI at srcLocation.
*
* @param referenceManager
* @param srcLocation the value of the <code>src</code> attribute of the <code>instance</code> element
*/
private static String getPath(ReferenceManager referenceManager, String srcLocation) throws InvalidReferenceException {
String uri = referenceManager.deriveReference(srcLocation).getLocalURI();
return uri.startsWith("//") /* todo why is this? */ ? uri.substring(1) : uri;
}

public interface Processor {

}

public interface ExternalDataInstanceProcessor extends ExternalInstanceParser.Processor {
void processInstance(@NotNull String id, @NotNull TreeElement root);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.javarosa.xform.parse;

public interface ExternalInstanceParserFactory {

ExternalInstanceParser getExternalInstanceParser();
}
1 change: 1 addition & 0 deletions src/main/java/org/javarosa/xform/parse/XFormParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ public class XFormParser implements IXFormParserFunctions {
private int serialQuestionID = 1;

private static IAnswerResolver answerResolver;

public static IAnswerResolver getAnswerResolver() {
return answerResolver;
}
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/org/javarosa/xform/util/XFormUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,23 @@
package org.javarosa.xform.util;

import org.javarosa.core.model.FormDef;
import org.javarosa.core.model.instance.TreeElement;
import org.javarosa.core.reference.InvalidReferenceException;
import org.javarosa.core.reference.ReferenceManager;
import org.javarosa.core.util.externalizable.DeserializationException;
import org.javarosa.core.util.externalizable.ExtUtil;
import org.javarosa.xform.parse.ExternalInstanceParser;
import org.javarosa.xform.parse.ExternalInstanceParserFactory;
import org.javarosa.xform.parse.IXFormParserFactory;
import org.javarosa.xform.parse.XFormParseException;
import org.javarosa.xform.parse.XFormParser;
import org.javarosa.xform.parse.XFormParserFactory;
import org.javarosa.xml.util.InvalidStructureException;
import org.javarosa.xml.util.UnfullfilledRequirementsException;
import org.kxml2.kdom.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmlpull.v1.XmlPullParserException;

import java.io.DataInputStream;
import java.io.FileReader;
Expand All @@ -46,13 +54,18 @@ public class XFormUtils {
private static final Logger logger = LoggerFactory.getLogger(XFormUtils.class);

private static IXFormParserFactory _factory = new XFormParserFactory();
private static ExternalInstanceParserFactory externalInstanceParserFactory = ExternalInstanceParser::new;

public static IXFormParserFactory setXFormParserFactory(IXFormParserFactory factory) {
IXFormParserFactory oldFactory = _factory;
_factory = factory;
return oldFactory;
}

public static void setExternalInstanceParserFactory(ExternalInstanceParserFactory factory) {
externalInstanceParserFactory = factory;
}

public static FormDef getFormFromResource (String resource) throws XFormParser.ParseException {
InputStream is = System.class.getResourceAsStream(resource);
if (is == null) {
Expand Down Expand Up @@ -166,6 +179,9 @@ public static FormDef getFormFromSerializedResource(String resource) {
return returnForm;
}

public static TreeElement getExternalInstance(ReferenceManager referenceManager, String id, String instanceSrc) throws UnfullfilledRequirementsException, InvalidStructureException, XmlPullParserException, IOException, InvalidReferenceException {
return externalInstanceParserFactory.getExternalInstanceParser().parse(referenceManager, id, instanceSrc);
}

/////Parser Attribute warning stuff

Expand Down
12 changes: 10 additions & 2 deletions src/test/java/org/javarosa/entities/EntitiesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import static org.javarosa.core.util.XFormsElement.mainInstance;
import static org.javarosa.core.util.XFormsElement.model;
import static org.javarosa.core.util.XFormsElement.select1;
import static org.javarosa.core.util.XFormsElement.setvalue;
import static org.javarosa.core.util.XFormsElement.t;
import static org.javarosa.core.util.XFormsElement.title;

Expand Down Expand Up @@ -93,11 +94,16 @@ public void fillingFormWithCreate_makesEntityAvailable() throws IOException, XFo
t("data id=\"create-entity-form\"",
t("name"),
t("meta",
t("entity dataset=\"people\" create=\"1\"")
t("entity dataset=\"people\" create=\"1\" id=\"\"",
t("label")
)
)
)
),
bind("/data/name").type("string").withAttribute("entities", "saveto", "name")
bind("/data/name").type("string").withAttribute("entities", "saveto", "name"),
bind("/data/meta/entity/@id").type("string"),
bind("/data/meta/entity/label").type("string").calculate("/data/name"),
setvalue("odk-instance-first-load", "/data/meta/entity/@id", "uuid()")
)
),
body(
Expand All @@ -114,6 +120,8 @@ public void fillingFormWithCreate_makesEntityAvailable() throws IOException, XFo
List<Entity> entities = scenario.getFormEntryController().getModel().getExtras().get(Entities.class).getEntities();
assertThat(entities.size(), equalTo(1));
assertThat(entities.get(0).dataset, equalTo("people"));
assertThat(entities.get(0).id, equalTo(scenario.answerOf("/data/meta/entity/@id").getValue()));
assertThat(entities.get(0).label, equalTo("Tom Wambsgans"));
assertThat(entities.get(0).properties, equalTo(asList(new Pair<>("name", "Tom Wambsgans"))));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public void parseFirstDatasetToCreate_findsCreateWithTrueString() throws XFormPa
XFormParser parser = new XFormParser(new InputStreamReader(new ByteArrayInputStream(form.asXml().getBytes())));
FormDef formDef = parser.parse(null);

String dataset = EntityFormParser.parseFirstDatasetToCreate(formDef.getMainInstance());
String dataset = EntityFormParser.parseFirstDatasetToCreate(EntityFormParser.getEntityElement(formDef.getMainInstance()));
assertThat(dataset, equalTo("people"));
}
}

0 comments on commit 312ff3c

Please sign in to comment.