diff --git a/src/org/opendatakit/aggregate/parser/BaseFormParserForJavaRosa.java b/src/org/opendatakit/aggregate/parser/BaseFormParserForJavaRosa.java index bf3a9f552..dfecb96f9 100644 --- a/src/org/opendatakit/aggregate/parser/BaseFormParserForJavaRosa.java +++ b/src/org/opendatakit/aggregate/parser/BaseFormParserForJavaRosa.java @@ -17,6 +17,7 @@ package org.opendatakit.aggregate.parser; +import java.io.File; import java.io.IOException; import java.io.Serializable; import java.io.StringReader; @@ -32,6 +33,7 @@ import org.javarosa.core.model.instance.FormInstance; import org.javarosa.core.model.instance.TreeElement; import org.javarosa.core.model.instance.TreeReference; +import org.javarosa.core.reference.ReferenceManager; import org.javarosa.core.services.PrototypeManager; import org.javarosa.model.xform.XFormsModule; import org.javarosa.xform.parse.XFormParser; @@ -342,7 +344,7 @@ private String extractBase64FieldEncryptionKey(TreeElement submissionElement) { * * @throws ODKIncompleteSubmissionData */ - protected BaseFormParserForJavaRosa(String existingXml, String existingTitle, boolean allowLegacy) + protected BaseFormParserForJavaRosa(String existingXml, String existingTitle, File mediaDirectory, boolean allowLegacy) throws ODKIncompleteSubmissionData { if (existingXml == null) { throw new ODKIncompleteSubmissionData(Reason.MISSING_XML); @@ -351,6 +353,8 @@ protected BaseFormParserForJavaRosa(String existingXml, String existingTitle, bo xml = existingXml; initializeJavaRosa(); + ReferenceManager.instance().reset(); + ReferenceManager.instance().addReferenceFactory(new MediaFileReferenceFactory(mediaDirectory)); XFormParserWithBindEnhancements xfp = parseFormDefinition(xml, this); try { @@ -568,8 +572,8 @@ private List getBindingsForTreeElement(TreeElement treeElement) { * encryption. * @throws ODKIncompleteSubmissionData */ - public static DifferenceResult compareXml(BaseFormParserForJavaRosa incomingParser, - String existingXml, String existingTitle, boolean isWithinUpdateWindow) + public static DifferenceResult compareXml(BaseFormParserForJavaRosa incomingParser, String existingXml, + String existingTitle, File mediaDirectory, boolean isWithinUpdateWindow) throws ODKIncompleteSubmissionData { if (incomingParser == null || existingXml == null) { throw new ODKIncompleteSubmissionData(Reason.MISSING_XML); @@ -584,7 +588,7 @@ public static DifferenceResult compareXml(BaseFormParserForJavaRosa incomingPars FormDef formDef1; FormDef formDef2; BaseFormParserForJavaRosa existingParser = new BaseFormParserForJavaRosa(existingXml, - existingTitle, true); + existingTitle, mediaDirectory, true); formDef1 = incomingParser.rootJavaRosaFormDef; formDef2 = existingParser.rootJavaRosaFormDef; if (formDef1 == null || formDef2 == null) { diff --git a/src/org/opendatakit/aggregate/parser/MediaFileReferenceFactory.java b/src/org/opendatakit/aggregate/parser/MediaFileReferenceFactory.java new file mode 100644 index 000000000..40f3d83ca --- /dev/null +++ b/src/org/opendatakit/aggregate/parser/MediaFileReferenceFactory.java @@ -0,0 +1,77 @@ +package org.opendatakit.aggregate.parser; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import org.javarosa.core.reference.InvalidReferenceException; +import org.javarosa.core.reference.Reference; +import org.javarosa.core.reference.ReferenceFactory; + +/** + * ReferenceFactory implementation that resolves any jr:// URI to the specified media folder. Most methods are + * unimplemented and throw UnsupportedOperationException if used. + */ +@SuppressWarnings("checkstyle:ParameterName") +public class MediaFileReferenceFactory implements ReferenceFactory { + private final File mediaDirectory; + + public MediaFileReferenceFactory(File mediaDirectory) { + this.mediaDirectory = mediaDirectory; + } + + @Override + public boolean derives(String URI) { + return true; + } + + @Override + public Reference derive(String URI) throws InvalidReferenceException { + return new Reference() { + @Override + public String getLocalURI() { + return mediaDirectory.getAbsolutePath() + URI.substring(URI.lastIndexOf('/')); + } + + @Override + public boolean doesBinaryExist() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public InputStream getStream() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public String getURI() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isReadOnly() { + return false; + } + + @Override + public OutputStream getOutputStream() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void remove() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public Reference[] probeAlternativeReferences() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public Reference derive(String URI, String context) throws InvalidReferenceException { + throw new UnsupportedOperationException(); + } +} diff --git a/src/org/opendatakit/briefcase/model/BriefcaseFormDefinition.java b/src/org/opendatakit/briefcase/model/BriefcaseFormDefinition.java index eef5332a2..f931022ad 100644 --- a/src/org/opendatakit/briefcase/model/BriefcaseFormDefinition.java +++ b/src/org/opendatakit/briefcase/model/BriefcaseFormDefinition.java @@ -17,6 +17,7 @@ package org.opendatakit.briefcase.model; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.opendatakit.briefcase.util.FileSystemUtils.getMediaDirectory; import java.io.BufferedReader; import java.io.File; @@ -160,7 +161,8 @@ public static BriefcaseFormDefinition resolveAgainstBriefcaseDefn(File tmpFormFi // newDefn is considered identical to what we have locally... result = DifferenceResult.XFORMS_IDENTICAL; } else { - result = JavaRosaParserWrapper.compareXml(newDefn, existingXml, existingTitle, true); + result = JavaRosaParserWrapper.compareXml(newDefn, existingXml, existingTitle, + getMediaDirectory(briefcaseFormDirectory), true); } if (result == DifferenceResult.XFORMS_DIFFERENT) { @@ -169,6 +171,7 @@ public static BriefcaseFormDefinition resolveAgainstBriefcaseDefn(File tmpFormFi newDefn, revisedXml, Objects.requireNonNull(revisedDefn).getFormName(), + getMediaDirectory(briefcaseFormDirectory), true ); if (result == DifferenceResult.XFORMS_DIFFERENT) { diff --git a/src/org/opendatakit/briefcase/util/FileSystemUtils.java b/src/org/opendatakit/briefcase/util/FileSystemUtils.java index 3e9bbf098..bc5719b89 100644 --- a/src/org/opendatakit/briefcase/util/FileSystemUtils.java +++ b/src/org/opendatakit/briefcase/util/FileSystemUtils.java @@ -164,14 +164,9 @@ public static File getFormDefinitionFile(File formDirectory) throws FileSystemEx return new File(formDirectory, formDirectory.getName() + ".xml"); } - static File getMediaDirectory(File formDirectory) - throws FileSystemException { - File mediaDir = new File(formDirectory, formDirectory.getName() + "-media"); - if (!mediaDir.exists() && !mediaDir.mkdirs()) { - throw new FileSystemException("unable to create directory: " + mediaDir.getAbsolutePath()); - } - - return mediaDir; + // May or may not actually exist on disk depending on whether the form definition has attached media. + public static File getMediaDirectory(File formDirectory) throws FileSystemException { + return new File(formDirectory, formDirectory.getName() + "-media"); } static File getFormInstancesDirectory(File formDirectory) throws FileSystemException { diff --git a/src/org/opendatakit/briefcase/util/JavaRosaParserWrapper.java b/src/org/opendatakit/briefcase/util/JavaRosaParserWrapper.java index 3453c8603..196254762 100644 --- a/src/org/opendatakit/briefcase/util/JavaRosaParserWrapper.java +++ b/src/org/opendatakit/briefcase/util/JavaRosaParserWrapper.java @@ -1,5 +1,7 @@ package org.opendatakit.briefcase.util; +import static org.opendatakit.briefcase.util.FileSystemUtils.getMediaDirectory; + import java.io.File; import org.javarosa.core.model.instance.TreeElement; import org.opendatakit.aggregate.exception.ODKIncompleteSubmissionData; @@ -10,7 +12,7 @@ public class JavaRosaParserWrapper extends BaseFormParserForJavaRosa { private final File formDefinitionFile; public JavaRosaParserWrapper(File formDefinitionFile, String inputXml) throws ODKIncompleteSubmissionData { - super(inputXml, null, true); + super(inputXml, null, getMediaDirectory(formDefinitionFile.getParentFile()), true); this.formDefinitionFile = formDefinitionFile; } diff --git a/test/java/org/opendatakit/briefcase/model/BriefcaseFormDefinitionWithExternalDataTest.java b/test/java/org/opendatakit/briefcase/model/BriefcaseFormDefinitionWithExternalDataTest.java new file mode 100644 index 000000000..ebe499d01 --- /dev/null +++ b/test/java/org/opendatakit/briefcase/model/BriefcaseFormDefinitionWithExternalDataTest.java @@ -0,0 +1,55 @@ +package org.opendatakit.briefcase.model; + +import static java.nio.file.Files.delete; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.opendatakit.briefcase.util.FileSystemUtils.getMediaDirectory; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.opendatakit.briefcase.util.BadFormDefinition; + +public class BriefcaseFormDefinitionWithExternalDataTest { + private Path formDir; + private Path formFile; + private Path mediaDir; + private Path mediaFile; + + @Before + public void setUp() { + try { + formDir = Files.createTempDirectory("briefcase_test"); + formFile = formDir.resolve("form.xml"); + Path sourceFile = Paths.get(BriefcaseFormDefinitionWithExternalDataTest.class.getResource("form-with-external-secondary-instance.xml").toURI()); + Files.copy(sourceFile, formFile); + + mediaDir = getMediaDirectory(formDir.toFile()).toPath(); + Files.createDirectories(mediaDir); + mediaFile = mediaDir.resolve("external-xml.xml"); + Path sourceMedia = Paths.get(BriefcaseFormDefinitionWithExternalDataTest.class.getResource("external-xml.xml").toURI()); + Files.copy(sourceMedia, mediaFile); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + + @Test + public void buildsFormDef_whenDefinitionReferencesExternalSecondaryInstance() throws BadFormDefinition { + BriefcaseFormDefinition briefcaseFormDefinition = new BriefcaseFormDefinition(formDir.toFile(), formFile.toFile()); + + assertThat(briefcaseFormDefinition.getFormName(), is("Form with external secondary instance")); + } + + @After + public void tearDown() throws IOException { + delete(formFile); + delete(mediaFile); + delete(mediaDir); + delete(formDir); + } +} diff --git a/test/resources/org/opendatakit/briefcase/model/external-xml.xml b/test/resources/org/opendatakit/briefcase/model/external-xml.xml new file mode 100644 index 000000000..add5d80ad --- /dev/null +++ b/test/resources/org/opendatakit/briefcase/model/external-xml.xml @@ -0,0 +1,14 @@ + + + + a + + + + b + + + + c + + \ No newline at end of file diff --git a/test/resources/org/opendatakit/briefcase/model/form-with-external-secondary-instance.xml b/test/resources/org/opendatakit/briefcase/model/form-with-external-secondary-instance.xml new file mode 100644 index 000000000..6f5251040 --- /dev/null +++ b/test/resources/org/opendatakit/briefcase/model/form-with-external-secondary-instance.xml @@ -0,0 +1,22 @@ + + + + Form with external secondary instance + + + + + + + + + + + + + + + + +