Skip to content

Commit

Permalink
Merge pull request #756 from seadowg/instance-interceptor
Browse files Browse the repository at this point in the history
Add ability for for clients to provide their own instance parsing
  • Loading branch information
seadowg committed Apr 26, 2024
2 parents 5ae6894 + ef7afa4 commit 8908e4a
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 43 deletions.
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
package org.javarosa.core.model.instance;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.io.input.BOMInputStream;
import org.javarosa.core.model.data.UncastData;
import org.javarosa.xform.parse.ExternalInstanceParser;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.io.input.BOMInputStream;
import org.javarosa.core.model.data.UncastData;

public class CsvExternalInstance {
public static TreeElement parse(String instanceId, String path) throws IOException {
public class CsvExternalInstance implements ExternalInstanceParser.FileInstanceParser {

public TreeElement parse(String instanceId, String path) throws IOException {
final TreeElement root = new TreeElement("root", 0);
root.setInstanceName(instanceId);

Expand Down Expand Up @@ -41,6 +44,11 @@ public static TreeElement parse(String instanceId, String path) throws IOExcepti
return root;
}

@Override
public boolean isSupported(String instanceId, String instanceSrc) {
return instanceSrc.contains("file-csv");
}

private static char getDelimiter(String path) throws IOException {
char delimiter = ',';
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.javarosa.core.model.instance.TreeElement;
import org.javarosa.xform.parse.ExternalInstanceParser;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Objects;
import org.javarosa.core.model.instance.TreeElement;

public class GeoJsonExternalInstance {
public static TreeElement parse(String instanceId, String path) throws IOException {
public class GeoJsonExternalInstance implements ExternalInstanceParser.FileInstanceParser {

public TreeElement parse(String instanceId, String path) throws IOException {
final TreeElement root = new TreeElement("root", 0);
root.setInstanceName(instanceId);

Expand Down Expand Up @@ -68,4 +71,9 @@ public static TreeElement parse(String instanceId, String path) throws IOExcepti

return root;
}

@Override
public boolean isSupported(String instanceId, String instanceSrc) {
return instanceSrc.endsWith("geojson");
}
}
34 changes: 31 additions & 3 deletions src/main/java/org/javarosa/xform/parse/ExternalInstanceParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,31 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static java.util.Arrays.asList;

public class ExternalInstanceParser {

private List<ExternalDataInstanceProcessor> externalDataInstanceProcessors = new ArrayList<>();
private List<FileInstanceParser> fileInstanceParsers = asList(
new CsvExternalInstance(),
new GeoJsonExternalInstance()
);

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);

Optional<FileInstanceParser> fileParser = fileInstanceParsers.stream()
.filter(fileInstanceParser -> fileInstanceParser.isSupported(instanceId, instanceSrc))
.findFirst();

TreeElement root;
if (fileParser.isPresent()) {
root = fileParser.get().parse(instanceId, path);
} else {
root = XmlExternalInstance.parse(instanceId, path);
}

for (ExternalDataInstanceProcessor processor : externalDataInstanceProcessors) {
processor.processInstance(instanceId, root);
Expand All @@ -36,6 +51,14 @@ public void addProcessor(Processor processor) {
externalDataInstanceProcessors.add((ExternalDataInstanceProcessor) processor);
}

/**
* Adds {@link FileInstanceParser} before others. The last added {@link FileInstanceParser} will be checked
* (via {@link FileInstanceParser#isSupported(String, String)}) first.
*/
public void addFileInstanceParser(FileInstanceParser fileInstanceParser) {
fileInstanceParsers.add(0, fileInstanceParser);
}

/**
* Returns the path of the URI at srcLocation.
*
Expand All @@ -54,4 +77,9 @@ public interface Processor {
public interface ExternalDataInstanceProcessor extends ExternalInstanceParser.Processor {
void processInstance(@NotNull String id, @NotNull TreeElement root);
}

public interface FileInstanceParser {
TreeElement parse(String instanceId, String path) throws IOException;
boolean isSupported(String instanceId, String instanceSrc);
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
package org.javarosa.core.model.instance;

import org.apache.commons.io.input.BOMInputStream;
import org.junit.Before;
import org.junit.Test;

import java.io.FileInputStream;
import java.io.IOException;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.javarosa.test.utils.ResourcePathHelper.r;
import static org.junit.Assert.assertEquals;

import java.io.FileInputStream;
import java.io.IOException;
import org.apache.commons.io.input.BOMInputStream;
import org.junit.Before;
import org.junit.Test;

public class CsvExternalInstanceTest {
private TreeElement commaSeparated;
private TreeElement semiColonSeparated;

@Before
public void setUp() throws IOException {
commaSeparated = CsvExternalInstance.parse("id", r("external-secondary-comma-complex.csv").toString());
semiColonSeparated = CsvExternalInstance.parse("id", r("external-secondary-semicolon-complex.csv").toString());
commaSeparated = new CsvExternalInstance().parse("id", r("external-secondary-comma-complex.csv").toString());
semiColonSeparated = new CsvExternalInstance().parse("id", r("external-secondary-semicolon-complex.csv").toString());
}

@Test
Expand Down Expand Up @@ -58,13 +59,13 @@ public void ignores_utf8_bom() throws IOException {
BOMInputStream bomIs = new BOMInputStream(new FileInputStream(r("external-secondary-csv-bom.csv").toFile()));
assertThat(bomIs.hasBOM(), is(true));

TreeElement bomCsv = CsvExternalInstance.parse("id", r("external-secondary-csv-bom.csv").toString());
TreeElement bomCsv = new CsvExternalInstance().parse("id", r("external-secondary-csv-bom.csv").toString());
assertThat(bomCsv.getChildAt(0).getChildAt(0).getName(), is("name"));
}

@Test
public void parses_utf8_characters() throws IOException {
TreeElement bomCsv = CsvExternalInstance.parse("id", r("external-secondary-csv-bom.csv").toString());
TreeElement bomCsv = new CsvExternalInstance().parse("id", r("external-secondary-csv-bom.csv").toString());
assertThat(bomCsv.getChildAt(0).getChild("elevation", 0).getValue().getValue(), is("testé"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,22 @@

package org.javarosa.core.model.instance.geojson;

import org.javarosa.core.model.instance.TreeElement;
import org.junit.Test;

import java.io.IOException;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.javarosa.test.utils.ResourcePathHelper.r;
import static org.junit.Assert.fail;

import java.io.IOException;
import org.javarosa.core.model.instance.TreeElement;
import org.junit.Test;

public class GeoJsonExternalInstanceTest {

@Test
public void parse_addsGeometriesAsChildren_forMultipleFeatures() throws IOException {
TreeElement featureCollection = GeoJsonExternalInstance.parse("id", r("feature-collection.geojson").toString());
TreeElement featureCollection = new GeoJsonExternalInstance().parse("id", r("feature-collection.geojson").toString());
assertThat(featureCollection.getNumChildren(), is(3));
assertThat(featureCollection.getChildAt(0).getChild("geometry", 0).getValue().getValue(), is("0.5 102 0 0"));
assertThat(featureCollection.getChildAt(1).getChild("geometry", 0).getValue().getValue(), is("0.5 104 0 0; 0.5 105 0 0"));
Expand All @@ -40,7 +41,7 @@ public void parse_addsGeometriesAsChildren_forMultipleFeatures() throws IOExcept
@Test
public void parse_throwsException_ifNoTopLevelObject() {
try {
GeoJsonExternalInstance.parse("id", r("not-object.geojson").toString());
new GeoJsonExternalInstance().parse("id", r("not-object.geojson").toString());
fail("Exception expected");
} catch (IOException e) {
// expected
Expand All @@ -50,7 +51,7 @@ public void parse_throwsException_ifNoTopLevelObject() {
@Test
public void parse_throwsException_ifTopLevelObjectTypeNotFeatureCollection() {
try {
GeoJsonExternalInstance.parse("id", r("invalid-type.geojson").toString());
new GeoJsonExternalInstance().parse("id", r("invalid-type.geojson").toString());
fail("Exception expected");
} catch (IOException e) {
// expected
Expand All @@ -59,20 +60,20 @@ public void parse_throwsException_ifTopLevelObjectTypeNotFeatureCollection() {

@Test
public void parse_ignoresExtraTopLevelProperties() throws IOException {
TreeElement featureCollection = GeoJsonExternalInstance.parse("id", r("feature-collection-extra-toplevel.geojson").toString());
TreeElement featureCollection = new GeoJsonExternalInstance().parse("id", r("feature-collection-extra-toplevel.geojson").toString());
assertThat(featureCollection.getChildAt(0).getNumChildren(), is(3));
}

@Test
public void parse_acceptsAnyToplevelPropertyOrder() throws IOException {
TreeElement featureCollection = GeoJsonExternalInstance.parse("id", r("feature-collection-toplevel-order.geojson").toString());
TreeElement featureCollection = new GeoJsonExternalInstance().parse("id", r("feature-collection-toplevel-order.geojson").toString());
assertThat(featureCollection.getChildAt(0).getNumChildren(), is(3));
}

@Test
public void parse_throwsException_ifNoFeaturesArray() {
try {
GeoJsonExternalInstance.parse("id", r("bad-futures-collection.geojson").toString());
new GeoJsonExternalInstance().parse("id", r("bad-futures-collection.geojson").toString());
fail("Exception expected");
} catch (IOException e) {
// expected
Expand All @@ -82,7 +83,7 @@ public void parse_throwsException_ifNoFeaturesArray() {
@Test
public void parse_throwsException_ifFeaturesNotArray() {
try {
GeoJsonExternalInstance.parse("id", r("bad-features-not-array.geojson").toString());
new GeoJsonExternalInstance().parse("id", r("bad-features-not-array.geojson").toString());
fail("Exception expected");
} catch (IOException e) {
// expected
Expand All @@ -92,7 +93,7 @@ public void parse_throwsException_ifFeaturesNotArray() {
@Test
public void parse_throwsException_ifSingleFeatureNotOfFeatureType() {
try {
GeoJsonExternalInstance.parse("id", r("bad-feature-not-feature.geojson").toString());
new GeoJsonExternalInstance().parse("id", r("bad-feature-not-feature.geojson").toString());
fail("Exception expected");
} catch (IOException e) {
// expected
Expand All @@ -101,7 +102,7 @@ public void parse_throwsException_ifSingleFeatureNotOfFeatureType() {

@Test
public void parse_addsAllOtherPropertiesAsChildren() throws IOException {
TreeElement featureCollection = GeoJsonExternalInstance.parse("id", r("feature-collection.geojson").toString());
TreeElement featureCollection = new GeoJsonExternalInstance().parse("id", r("feature-collection.geojson").toString());
assertThat(featureCollection.getChildAt(0).getNumChildren(), is(4));
assertThat(featureCollection.getChildAt(0).getChild("name", 0).getValue().getValue(), is("My cool point"));

Expand All @@ -111,44 +112,44 @@ public void parse_addsAllOtherPropertiesAsChildren() throws IOException {

@Test
public void parse_usesTopLevelId() throws IOException {
TreeElement featureCollection = GeoJsonExternalInstance.parse("id", r("feature-collection-id-toplevel.geojson").toString());
TreeElement featureCollection = new GeoJsonExternalInstance().parse("id", r("feature-collection-id-toplevel.geojson").toString());
assertThat(featureCollection.getChildAt(0).getNumChildren(), is(4));
assertThat(featureCollection.getChildAt(0).getChild("id", 0).getValue().getValue(), is("top-level-id"));

}

@Test
public void parse_prioritizesTopLevelId() throws IOException {
TreeElement featureCollection = GeoJsonExternalInstance.parse("id", r("feature-collection-id-twice.geojson").toString());
TreeElement featureCollection = new GeoJsonExternalInstance().parse("id", r("feature-collection-id-twice.geojson").toString());
assertThat(featureCollection.getChildAt(0).getNumChildren(), is(4));
assertThat(featureCollection.getChildAt(0).getChild("id", 0).getValue().getValue(), is("top-level-id"));
}

@Test
public void parse_allowsIntegerId() throws IOException {
TreeElement featureCollection = GeoJsonExternalInstance.parse("id", r("feature-collection-integer-id.geojson").toString());
TreeElement featureCollection = new GeoJsonExternalInstance().parse("id", r("feature-collection-integer-id.geojson").toString());

assertThat(featureCollection.getChildAt(0).getNumChildren(), is(4));
assertThat(featureCollection.getChildAt(0).getChild("id", 0).getValue().getValue(), is("77"));
}

@Test
public void parse_ignoresUnknownToplevelProperties() throws IOException {
TreeElement featureCollection = GeoJsonExternalInstance.parse("id", r("feature-collection-extra-feature-toplevel.geojson").toString());
TreeElement featureCollection = new GeoJsonExternalInstance().parse("id", r("feature-collection-extra-feature-toplevel.geojson").toString());
assertThat(featureCollection.getChildAt(0).getNumChildren(), is(3));
assertThat(featureCollection.getChildAt(0).getChild("ignored", 0), nullValue());
}

@Test
public void parse_addsFeaturesWithNoProperties() throws IOException {
TreeElement featureCollection = GeoJsonExternalInstance.parse("id", r("feature-collection-no-properties.geojson").toString());
TreeElement featureCollection = new GeoJsonExternalInstance().parse("id", r("feature-collection-no-properties.geojson").toString());
assertThat(featureCollection.getChildAt(0).getNumChildren(), is(1));
}

@Test
public void parse_throwsException_whenGeometryNotSupported() {
try {
GeoJsonExternalInstance.parse("id", r("feature-collection-with-unsupported-type.geojson").toString());
new GeoJsonExternalInstance().parse("id", r("feature-collection-with-unsupported-type.geojson").toString());
fail("Exception expected");
} catch (IOException e) {
// expected
Expand All @@ -157,7 +158,7 @@ public void parse_throwsException_whenGeometryNotSupported() {

@Test
public void parse_allowsNullFeaturePropertyValues() throws IOException {
TreeElement featureCollection = GeoJsonExternalInstance.parse("id", r("feature-collection-with-null.geojson").toString());
TreeElement featureCollection = new GeoJsonExternalInstance().parse("id", r("feature-collection-with-null.geojson").toString());
assertThat(featureCollection.getChildAt(0).getNumChildren(), is(4));
assertThat(featureCollection.getChildAt(0).getChild("extra", 0).getValue().getDisplayText(), is(""));
}
Expand Down

0 comments on commit 8908e4a

Please sign in to comment.