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

Expose created entities after finalizing form #691

Merged
merged 36 commits into from
Oct 4, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
c15d279
Add dummy entities API for Collect
seadowg Sep 9, 2022
0798659
Spike out exposing created entity once form is finalized
seadowg Sep 9, 2022
19e83e7
Make sure entities are only created when they should be
seadowg Sep 13, 2022
c6e0d57
Support caching form def/instance with entities
seadowg Sep 13, 2022
6b458a7
Move back to using List for saveto refs
seadowg Sep 13, 2022
0b036bd
Remove id attribute from test forms
seadowg Sep 15, 2022
7b31a82
Make sure namespace prefix isn't hardcoded for entities
seadowg Sep 21, 2022
d4f3c49
Add test for selects
seadowg Sep 21, 2022
b547b78
Reimplement entity parsing and processing using plugin style architec…
seadowg Sep 21, 2022
b6ba302
Ignore entities:create if the namespace is incorrect
seadowg Sep 23, 2022
15a0861
Don't create entities if create is not relevant
seadowg Sep 23, 2022
444143d
Only parse dataset out during processing
seadowg Sep 23, 2022
8072261
Don't return all matching children in cases we don't expect more than…
seadowg Sep 23, 2022
a8e5023
Use forEach supported by Android API 21
seadowg Sep 23, 2022
488511c
Optimize imports
seadowg Sep 23, 2022
98aac67
Pull out common Extras
seadowg Sep 30, 2022
4aec176
Rename method
seadowg Sep 30, 2022
4497074
Rename field
seadowg Sep 30, 2022
ee3e78d
Make naming less ambiguous
seadowg Sep 30, 2022
de9ad87
Validate namespace for create action
seadowg Sep 30, 2022
96a057b
Add convenience method for getting namespaced children
seadowg Sep 30, 2022
d8d28c6
Move test
seadowg Sep 30, 2022
43fff8d
Ignore saveto attributes with incorrect namespaces
seadowg Sep 30, 2022
eb6568e
Use simpler parsing code
seadowg Sep 30, 2022
6946ecc
Make it easier to add processor that deals with multiple stages of parse
seadowg Sep 30, 2022
ced91b0
Validate entity version if defined in model
seadowg Sep 30, 2022
71b75af
Keep 'processor' consistent in naming
seadowg Oct 3, 2022
c3f86f2
Make sure forms without entities can be parsed
seadowg Oct 3, 2022
331acd6
Naming improvements
seadowg Oct 3, 2022
e978f64
Remove unsupported forEach
seadowg Oct 3, 2022
d4b9100
Simplify ExternalizableExtras
seadowg Oct 3, 2022
35652f6
Make sure only bind attributes with correct namespace are removed
seadowg Oct 3, 2022
f69c9b2
Add explicit parse exception
seadowg Oct 3, 2022
9b34f4c
Remove IOException from parse() method
seadowg Oct 3, 2022
c20aa0b
Correct version
seadowg Oct 3, 2022
9afa909
Check that parsing works with patch versions
seadowg Oct 4, 2022
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.javarosa.entities;

import org.javarosa.xform.parse.XFormParseException;

public class UnrecognizedEntityVersionException extends XFormParseException {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,55 @@
import kotlin.Pair;
import org.javarosa.core.model.DataBinding;
import org.javarosa.core.model.FormDef;
import org.javarosa.core.model.instance.FormInstance;
import org.javarosa.core.model.instance.TreeElement;
import org.javarosa.entities.UnrecognizedEntityVersionException;
import org.javarosa.model.xform.XPathReference;
import org.javarosa.xform.parse.XFormParseException;
import org.javarosa.xform.parse.XFormParser;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class EntityFormParseProcessor implements XFormParser.BindAttributeProcessor, XFormParser.FormDefProcessor {
public class EntityFormParseProcessor implements XFormParser.BindAttributeProcessor, XFormParser.FormDefProcessor, XFormParser.ModelAttributeProcessor {

private static final String ENTITIES_NAMESPACE = "http://www.opendatakit.org/xforms/entities";
public static final String SUPPORTED_VERSION = "v2022.1";

private final List<Pair<XPathReference, String>> saveTos = new ArrayList<>();

@Override
public Set<Pair<String, String>> getUsedAttributes() {
public Set<Pair<String, String>> getUsedModelAttributes() {
HashSet<Pair<String, String>> attributes = new HashSet<>();
attributes.add(new Pair<>(ENTITIES_NAMESPACE, "entities-version"));

return attributes;
}

@Override
public void processModelAttribute(String name, String value) throws XFormParseException {
try {
String[] versionParts = value.split("\\.");
if (!SUPPORTED_VERSION.equals(versionParts[0] + "." + versionParts[1])) {
throw new UnrecognizedEntityVersionException();
}
} catch (ArrayIndexOutOfBoundsException e) {
throw new UnrecognizedEntityVersionException();
}
}

@Override
public Set<Pair<String, String>> getUsedBindAttributes() {
HashSet<Pair<String, String>> attributes = new HashSet<>();
attributes.add(new Pair<>(ENTITIES_NAMESPACE, "saveto"));

return attributes;
}

@Override
public void processBindingAttribute(String name, String value, DataBinding binding) {
public void processBindAttribute(String name, String value, DataBinding binding) {
saveTos.add(new Pair<>((XPathReference) binding.getReference(), value));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,15 @@ DataBinding createBinding(IXFormParserFunctions parserFunctions, FormDef formDef
String namespace = element.getAttributeNamespace(i);
String name = element.getAttributeName(i);

if (bindAttributeProcessor.getUsedAttributes().contains(new Pair<>(namespace, name))) {
bindAttributeProcessor.processBindingAttribute(name, element.getAttributeValue(i), binding);
if (bindAttributeProcessor.getUsedBindAttributes().contains(new Pair<>(namespace, name))) {
bindAttributeProcessor.processBindAttribute(name, element.getAttributeValue(i), binding);
}
}
});

List<String> processorAttributes = bindAttributeProcessors.stream()
.flatMap((Function<XFormParser.BindAttributeProcessor, Stream<String>>) bindAttributeProcessor -> {
return bindAttributeProcessor.getUsedAttributes().stream().map(Pair::getSecond);
return bindAttributeProcessor.getUsedBindAttributes().stream().map(Pair::getSecond);
})
.collect(Collectors.toList());

Expand Down
34 changes: 31 additions & 3 deletions src/main/java/org/javarosa/xform/parse/XFormParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ public class XFormParser implements IXFormParserFunctions {

private final List<BindAttributeProcessor> bindAttributeProcessors = new ArrayList<>();
private final List<FormDefProcessor> formDefProcessors = new ArrayList<>();
private final List<ModelAttributeProcessor> modelAttributeProcessors = new ArrayList<>();

/**
* The string IDs of all instances that are referenced in a instance() function call in the primary instance
Expand Down Expand Up @@ -413,6 +414,10 @@ public void addProcessor(Processor processor) {
if (processor instanceof FormDefProcessor) {
addFormDefProcessor((FormDefProcessor) processor);
}

if (processor instanceof ModelAttributeProcessor) {
addModelAttributeProcessor((ModelAttributeProcessor) processor);
}
}

public void addBindAttributeProcessor(BindAttributeProcessor bindAttributeProcessor) {
Expand All @@ -423,6 +428,10 @@ public void addFormDefProcessor(FormDefProcessor formDefProcessor) {
formDefProcessors.add(formDefProcessor);
}

public void addModelAttributeProcessor(ModelAttributeProcessor modelAttributeProcessor) {
modelAttributeProcessors.add(modelAttributeProcessor);
}

/**
* Extracts the namespaces from the given element and creates a map of URI to prefix
*/
Expand Down Expand Up @@ -673,7 +682,19 @@ private void parseMeta(Element e) {
}

//for ease of parsing, we assume a model comes before the controls, which isn't necessarily mandated by the xforms spec
private void parseModel(Element e) {
private void parseModel(Element e) throws XFormParseException {
modelAttributeProcessors.stream().forEach(processor -> {
for (int i = 0; i < e.getAttributeCount(); i++) {
Copy link
Member

Choose a reason for hiding this comment

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

I'm always terrified to see any kind of nested iteration but I guess we don't really care too much about form parse time (within reason) and we generally expect at most a handful of attributes and processors.

String namespace = e.getAttributeNamespace(i);
String name = e.getAttributeName(i);
String value = e.getAttributeValue(i);

if (processor.getUsedModelAttributes().contains(new Pair<>(namespace, name))) {
processor.processModelAttribute(name, value);
}
}
});

List<String> usedAtts = new ArrayList<>(); //no attributes parsed in title.
List<Element> delayedParseElements = new ArrayList<>();

Expand Down Expand Up @@ -2405,8 +2426,15 @@ public interface FormDefProcessor extends Processor {

public interface BindAttributeProcessor extends Processor {

Set<Pair<String, String>> getUsedAttributes();
Set<Pair<String, String>> getUsedBindAttributes();

void processBindAttribute(String name, String value, DataBinding binding);
}

public interface ModelAttributeProcessor extends Processor {

Set<Pair<String, String>> getUsedModelAttributes();

void processBindingAttribute(String name, String value, DataBinding binding);
void processModelAttribute(String name, String value) throws XFormParseException;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import kotlin.Pair;
import org.javarosa.core.model.FormDef;
import org.javarosa.core.util.XFormsElement;
import org.javarosa.entities.UnrecognizedEntityVersionException;
import org.javarosa.xform.parse.XFormParser;
import org.junit.Test;

Expand All @@ -25,6 +26,76 @@

public class EntityFormParseProcessorTest {

@Test(expected = UnrecognizedEntityVersionException.class)
public void whenVersionIsNotRecognized_throwsException() throws IOException {
XFormsElement form = XFormsElement.html(
asList(
new Pair<>("entities", "http://www.opendatakit.org/xforms/entities")
),
head(
title("Create entity form"),
t("model entities:entities-version=\"somethingElse\"",
mainInstance(
t("data id=\"create-entity-form\"",
t("name"),
t("orx:meta",
t("entities:entity dataset=\"people\"",
t("entities:create")
)
)
)
),
bind("/data/name").type("string").withAttribute("entities", "saveto", "name")
)
),
body(
input("/data/name")
)
);

EntityFormParseProcessor processor = new EntityFormParseProcessor();

XFormParser parser = new XFormParser(new InputStreamReader(new ByteArrayInputStream(form.asXml().getBytes())));
parser.addProcessor(processor);
parser.parse(null);
}

@Test
public void whenVersionIsNewPatch_doesNotThrowException() throws IOException {
String newPatchVersion = EntityFormParseProcessor.SUPPORTED_VERSION + ".12";

XFormsElement form = XFormsElement.html(
asList(
new Pair<>("entities", "http://www.opendatakit.org/xforms/entities")
),
head(
title("Create entity form"),
t("model entities:entities-version=\"" + newPatchVersion + "\"",
mainInstance(
t("data id=\"create-entity-form\"",
t("name"),
t("orx:meta",
t("entities:entity dataset=\"people\"",
t("entities:create")
)
)
)
),
bind("/data/name").type("string").withAttribute("entities", "saveto", "name")
)
),
body(
input("/data/name")
)
);

EntityFormParseProcessor processor = new EntityFormParseProcessor();

XFormParser parser = new XFormParser(new InputStreamReader(new ByteArrayInputStream(form.asXml().getBytes())));
parser.addProcessor(processor);
parser.parse(null);
Copy link
Member

Choose a reason for hiding this comment

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

You don't want to add any kind of assertion here? I think this is fine but I feel like I've seen you throw back tests like this so want to give you a chance to consider it again in case you do care.

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess it came from writing these out in order (in a TDD red-green cycle). The only needed a test where something didn't explode to get the code where I wanted, but you're right that it's a bit confusing to read after the fact. I'll add a "this is working as expected" assertion.

}

@Test
public void saveTosWithIncorrectNamespaceAreIgnored() throws IOException {
XFormsElement form = XFormsElement.html(
Expand Down