Search in sources :

Example 1 with ODKIncompleteSubmissionData

use of org.opendatakit.aggregate.exception.ODKIncompleteSubmissionData in project briefcase by opendatakit.

the class BriefcaseFormDefinition method resolveAgainstBriefcaseDefn.

public static final BriefcaseFormDefinition resolveAgainstBriefcaseDefn(File tmpFormFile, boolean copyFile) throws BadFormDefinition {
    if (!tmpFormFile.exists()) {
        throw new BadFormDefinition("Form directory does not contain form");
    }
    // parse the temp file into a form definition...
    boolean badForm = false;
    JavaRosaParserWrapper newDefn;
    File briefcaseFormDirectory;
    File briefcaseFormFile;
    try {
        newDefn = new JavaRosaParserWrapper(tmpFormFile, readFile(tmpFormFile));
        briefcaseFormDirectory = FileSystemUtils.getFormDirectory(newDefn.getFormName());
        briefcaseFormFile = FileSystemUtils.getFormDefinitionFile(briefcaseFormDirectory);
    } catch (ODKIncompleteSubmissionData e) {
        log.warn("bad form definition", e);
        try {
            badForm = true;
            newDefn = null;
            briefcaseFormDirectory = FileSystemUtils.getFormDirectory("_badForm");
            briefcaseFormFile = FileSystemUtils.getFormDefinitionFile(briefcaseFormDirectory);
        } catch (FileSystemException ex) {
            log.error("failed to establish storage location for bad form", e);
            throw new BadFormDefinition(ex);
        }
    } catch (FileSystemException e) {
        log.error("failed to establish storage location for form", e);
        throw new BadFormDefinition(e);
    }
    boolean isIdentical = false;
    boolean needsMediaUpdate = false;
    File revised = new File(briefcaseFormFile.getParentFile(), briefcaseFormFile.getName() + ".revised");
    String revisedXml = null;
    JavaRosaParserWrapper revisedDefn = null;
    // determine the most up-to-date existing definition...
    JavaRosaParserWrapper existingDefn;
    try {
        if (revised.exists()) {
            revisedXml = readFile(revised);
            revisedDefn = new JavaRosaParserWrapper(revised, revisedXml);
        }
        if (!briefcaseFormFile.exists()) {
            // Rename it to formFile and parse it.
            if (copyFile) {
                try {
                    FileUtils.copyFile(tmpFormFile, briefcaseFormFile);
                } catch (IOException e) {
                    String msg = "Unable to copy form definition file into briefcase directory";
                    log.error(msg, e);
                    throw new BadFormDefinition(msg);
                }
            } else {
                if (!tmpFormFile.renameTo(briefcaseFormFile)) {
                    // if cannot rename, try to copy instead (and mark for deletion)
                    try {
                        FileUtils.copyFile(tmpFormFile, briefcaseFormFile);
                        tmpFormFile.deleteOnExit();
                    } catch (IOException e) {
                        String msg = "Form directory does not contain form (can neither rename nor copy into briefcase directory)";
                        log.error(msg);
                        throw new BadFormDefinition(msg);
                    }
                }
            }
            // weird if it does...
            needsMediaUpdate = !revised.exists();
            existingDefn = new JavaRosaParserWrapper(briefcaseFormFile, readFile(briefcaseFormFile));
        } else {
            // get the current existing definition...
            String existingXml = readFile(briefcaseFormFile);
            existingDefn = new JavaRosaParserWrapper(briefcaseFormFile, existingXml);
            String existingTitle = existingDefn.getFormName();
            // compare the two
            DifferenceResult result;
            if (badForm) {
                // newDefn is considered identical to what we have locally...
                result = DifferenceResult.XFORMS_IDENTICAL;
            } else {
                result = JavaRosaParserWrapper.compareXml(newDefn, existingXml, existingTitle, true);
            }
            if (result == DifferenceResult.XFORMS_DIFFERENT) {
                if (revised.exists()) {
                    result = JavaRosaParserWrapper.compareXml(newDefn, revisedXml, revisedDefn.getFormName(), true);
                    if (result == DifferenceResult.XFORMS_DIFFERENT) {
                        throw new BadFormDefinition("Form definitions are incompatible.");
                    } else if (result != DifferenceResult.XFORMS_EARLIER_VERSION && result != DifferenceResult.XFORMS_MISSING_VERSION && result != DifferenceResult.XFORMS_IDENTICAL) {
                        if (copyFile) {
                            try {
                                FileUtils.copyFile(tmpFormFile, revised);
                            } catch (IOException e) {
                                String msg = "Unable to overwrite the '.revised' form definition file in briefcase storage";
                                log.error(msg, e);
                                throw new BadFormDefinition(msg);
                            }
                        } else {
                            if (!tmpFormFile.renameTo(revised)) {
                                // if cannot rename, try to copy instead (and mark for deletion)
                                try {
                                    FileUtils.copyFile(tmpFormFile, revised);
                                    tmpFormFile.deleteOnExit();
                                } catch (IOException e) {
                                    String msg = "Form directory does not contain form (can neither rename nor copy into briefcase directory)";
                                    log.error(msg, e);
                                    throw new BadFormDefinition(msg);
                                }
                            }
                        }
                        needsMediaUpdate = true;
                        // and re-parse the new revised file (since we just updated it...)
                        revisedDefn = new JavaRosaParserWrapper(revised, readFile(revised));
                    } else if (result == DifferenceResult.XFORMS_IDENTICAL) {
                        // confirm that the media is up-to-date when the forms are
                        // identical
                        // allows briefcase to resume a form download when it failed
                        // during
                        // the early form-media-fetch phases.
                        isIdentical = true;
                        needsMediaUpdate = true;
                    }
                } else {
                    throw new BadFormDefinition("Form definitions are incompatible.");
                }
            } else if (result != DifferenceResult.XFORMS_EARLIER_VERSION && result != DifferenceResult.XFORMS_MISSING_VERSION && result != DifferenceResult.XFORMS_IDENTICAL) {
                if (!revised.exists()) {
                    // overwrite everything and re-parse the new file.
                    if (copyFile) {
                        try {
                            FileUtils.copyFile(tmpFormFile, briefcaseFormFile);
                        } catch (IOException e) {
                            String msg = "Unable to overwrite form definition file in briefcase storage";
                            log.error(msg, e);
                            throw new BadFormDefinition(msg);
                        }
                    } else {
                        if (!tmpFormFile.renameTo(briefcaseFormFile)) {
                            // if cannot rename, try to copy instead (and mark for deletion)
                            try {
                                FileUtils.copyFile(tmpFormFile, briefcaseFormFile);
                                tmpFormFile.deleteOnExit();
                            } catch (IOException e) {
                                String msg = "Form directory does not contain form (can neither rename nor copy into briefcase directory)";
                                log.error(msg, e);
                                throw new BadFormDefinition(msg);
                            }
                        }
                    }
                    needsMediaUpdate = true;
                    // and re-parse the new form file (since we just updated it...)
                    existingXml = readFile(briefcaseFormFile);
                    existingDefn = new JavaRosaParserWrapper(briefcaseFormFile, existingXml);
                }
            } else if (result == DifferenceResult.XFORMS_IDENTICAL) {
                // if a revised form exists, we assume the media is up-to-date in that
                // folder. Otherwise, confirm that the media is up-to-date when the
                // forms are identical. This allows briefcase to resume a form
                // download
                // when it failed during the early form-media-fetch phases.
                isIdentical = true;
                needsMediaUpdate = !revised.exists();
            }
        }
    } catch (ODKIncompleteSubmissionData e) {
        throw new BadFormDefinition(e, e.getReason());
    }
    BriefcaseFormDefinition defn;
    if (revised.exists()) {
        defn = new BriefcaseFormDefinition(briefcaseFormDirectory, revisedDefn, revised, needsMediaUpdate);
    } else {
        defn = new BriefcaseFormDefinition(briefcaseFormDirectory, existingDefn, null, needsMediaUpdate);
    }
    if (!isIdentical && needsMediaUpdate) {
        EventBus.publish(new UpdatedBriefcaseFormDefinitionEvent(defn));
    }
    return defn;
}
Also used : DifferenceResult(org.opendatakit.aggregate.parser.BaseFormParserForJavaRosa.DifferenceResult) ODKIncompleteSubmissionData(org.opendatakit.aggregate.exception.ODKIncompleteSubmissionData) IOException(java.io.IOException) BadFormDefinition(org.opendatakit.briefcase.util.BadFormDefinition) JavaRosaParserWrapper(org.opendatakit.briefcase.util.JavaRosaParserWrapper) File(java.io.File)

Example 2 with ODKIncompleteSubmissionData

use of org.opendatakit.aggregate.exception.ODKIncompleteSubmissionData in project briefcase by opendatakit.

the class BaseFormParserForJavaRosa method parseFormDefinition.

private static final synchronized XFormParserWithBindEnhancements parseFormDefinition(String xml, BaseFormParserForJavaRosa parser) throws ODKIncompleteSubmissionData {
    StringReader isr = null;
    try {
        isr = new StringReader(xml);
        Document doc = XFormParser.getXMLDocument(isr);
        return new XFormParserWithBindEnhancements(parser, doc);
    } catch (Exception e) {
        throw new ODKIncompleteSubmissionData(e, Reason.BAD_JR_PARSE);
    } finally {
        isr.close();
    }
}
Also used : StringReader(java.io.StringReader) ODKIncompleteSubmissionData(org.opendatakit.aggregate.exception.ODKIncompleteSubmissionData) Document(org.kxml2.kdom.Document) IOException(java.io.IOException) FileNotFoundException(java.io.FileNotFoundException)

Example 3 with ODKIncompleteSubmissionData

use of org.opendatakit.aggregate.exception.ODKIncompleteSubmissionData in project briefcase by opendatakit.

the class BaseFormParserForJavaRosa method compareXml.

/**
 * Compare two XML files to assess their level of structural difference (if
 * any).
 *
 * @param incomingParser -- parsed version of incoming form
 * @param existingXml    -- the existing Xml for this form
 * @return XFORMS_SHARE_INSTANCE when bodies differ but instances and bindings
 *     are identical; XFORMS_SHARE_SCHEMA when bodies and/or bindings
 *     differ, but database structure remains unchanged; XFORMS_DIFFERENT
 *     when forms are different enough to affect database structure and/or
 *     encryption.
 * @throws ODKIncompleteSubmissionData
 */
public static DifferenceResult compareXml(BaseFormParserForJavaRosa incomingParser, String existingXml, String existingTitle, boolean isWithinUpdateWindow) throws ODKIncompleteSubmissionData {
    if (incomingParser == null || existingXml == null) {
        throw new ODKIncompleteSubmissionData(Reason.MISSING_XML);
    }
    // generally only the case within Briefcase
    if (incomingParser.xml.equals(existingXml)) {
        return DifferenceResult.XFORMS_IDENTICAL;
    }
    // parse XML
    FormDef formDef1;
    FormDef formDef2;
    BaseFormParserForJavaRosa existingParser = new BaseFormParserForJavaRosa(existingXml, existingTitle, true);
    formDef1 = incomingParser.rootJavaRosaFormDef;
    formDef2 = existingParser.rootJavaRosaFormDef;
    if (formDef1 == null || formDef2 == null) {
        throw new ODKIncompleteSubmissionData("Javarosa failed to construct a FormDef.  Is this an XForm definition?", Reason.BAD_JR_PARSE);
    }
    // check that the version is advancing from the earlier
    // form upload. The comparison is string-based, not
    // numeric-based (OpenRosa compliance). The recommended
    // version format is: yyyymmddnn e.g., 2012060100
    String ivs = incomingParser.rootElementDefn.versionString;
    if (ivs == null) {
        // if we are changing the file, the new file must have a version string
        return DifferenceResult.XFORMS_MISSING_VERSION;
    }
    String evs = existingParser.rootElementDefn.versionString;
    boolean modelVersionSame = (incomingParser.rootElementDefn.modelVersion == null) ? (existingParser.rootElementDefn.modelVersion == null) : incomingParser.rootElementDefn.modelVersion.equals(existingParser.rootElementDefn.modelVersion);
    boolean isEarlierVersion = false;
    if (!(evs == null || (modelVersionSame && ivs.length() > evs.length()) || (!modelVersionSame && ivs.compareTo(evs) > 0))) {
        // disallow updates if none of the following applies:
        // (1) if the existing form does not have a version (the new one does).
        // (2) if the existing form and new form have the same model version
        // and the new form has more leading zeros.
        // (3) if the existing form and new form have different model versions
        // and the new version string is lexically greater than the old one.
        isEarlierVersion = true;
        return DifferenceResult.XFORMS_EARLIER_VERSION;
    }
    /*
     * Changes in encryption (either on or off, or change in key) are a major
     * change. We could allow the public key to be revised, but most users won't
     * understand that this is possible or know how to do it.
     *
     * Ignore whether a submission profile is present or absent provided it does
     * not affect encryption or change the portion of the form being returned.
     */
    SubmissionProfile subProfile1 = formDef1.getSubmissionProfile();
    SubmissionProfile subProfile2 = formDef2.getSubmissionProfile();
    if (subProfile1 != null && subProfile2 != null) {
        // we have two profiles -- check that any encryption key matches...
        String publicKey1 = subProfile1.getAttribute(BASE64_RSA_PUBLIC_KEY);
        String publicKey2 = subProfile2.getAttribute(BASE64_RSA_PUBLIC_KEY);
        if (publicKey1 != null && publicKey2 != null) {
            // both have encryption
            if (!publicKey1.equals(publicKey2)) {
                // keys differ
                return (DifferenceResult.XFORMS_DIFFERENT);
            }
        } else if (publicKey1 != null || publicKey2 != null) {
            // one or the other has encryption (and the other doesn't)...
            return (DifferenceResult.XFORMS_DIFFERENT);
        }
        // get the TreeElement (e1, e2) that identifies the portion of the form
        // that will be submitted to Aggregate.
        IDataReference r;
        r = subProfile1.getRef();
        AbstractTreeElement<?> e1 = (r != null) ? formDef1.getInstance().resolveReference(r) : null;
        r = subProfile2.getRef();
        AbstractTreeElement<?> e2 = (r != null) ? formDef2.getInstance().resolveReference(r) : null;
        if (e1 != null && e2 != null) {
            // Ignore all namespace differences (Aggregate ignores them)...
            while (e1 != null && e2 != null) {
                if (!e1.getName().equals(e2.getName())) {
                    return (DifferenceResult.XFORMS_DIFFERENT);
                }
                e1 = e1.getParent();
                e2 = e2.getParent();
            }
            if (e1 != null || e2 != null) {
                // they should both terminate at the same time...
                return (DifferenceResult.XFORMS_DIFFERENT);
            }
        // we may still have differences, but if the overall form
        // is identical, we are golden...
        } else if (e1 != null || e2 != null) {
            // one returns a portion of the form and the other doesn't
            return (DifferenceResult.XFORMS_DIFFERENT);
        }
    } else if (subProfile1 != null) {
        if (subProfile1.getAttribute(BASE64_RSA_PUBLIC_KEY) != null) {
            // xml1 does encryption, the other doesn't
            return (DifferenceResult.XFORMS_DIFFERENT);
        }
        IDataReference r = subProfile1.getRef();
        if (r != null && formDef1.getInstance().resolveReference(r) != null) {
            // xml1 returns a portion of the form, the other doesn't
            return (DifferenceResult.XFORMS_DIFFERENT);
        }
    } else if (subProfile2 != null) {
        if (subProfile2.getAttribute(BASE64_RSA_PUBLIC_KEY) != null) {
            // xml2 does encryption, the other doesn't
            return (DifferenceResult.XFORMS_DIFFERENT);
        }
        IDataReference r = subProfile2.getRef();
        if (r != null && formDef2.getInstance().resolveReference(r) != null) {
            // xml2 returns a portion of the form, the other doesn't
            return (DifferenceResult.XFORMS_DIFFERENT);
        }
    }
    // get data model to compare instances
    FormInstance dataModel1 = formDef1.getInstance();
    FormInstance dataModel2 = formDef2.getInstance();
    if (dataModel1 == null || dataModel2 == null) {
        throw new ODKIncompleteSubmissionData("Javarosa failed to construct a FormInstance.  Is this an XForm definition?", Reason.BAD_JR_PARSE);
    }
    // return result of element-by-element instance/binding comparison
    DifferenceResult rc = compareTreeElements(dataModel1.getRoot(), incomingParser, dataModel2.getRoot(), existingParser);
    if (DifferenceResult.XFORMS_DIFFERENT == rc) {
        return rc;
    } else if (isEarlierVersion) {
        return DifferenceResult.XFORMS_EARLIER_VERSION;
    } else {
        return rc;
    }
}
Also used : FormDef(org.javarosa.core.model.FormDef) IDataReference(org.javarosa.core.model.IDataReference) ODKIncompleteSubmissionData(org.opendatakit.aggregate.exception.ODKIncompleteSubmissionData) SubmissionProfile(org.javarosa.core.model.SubmissionProfile) FormInstance(org.javarosa.core.model.instance.FormInstance)

Aggregations

ODKIncompleteSubmissionData (org.opendatakit.aggregate.exception.ODKIncompleteSubmissionData)3 IOException (java.io.IOException)2 File (java.io.File)1 FileNotFoundException (java.io.FileNotFoundException)1 StringReader (java.io.StringReader)1 FormDef (org.javarosa.core.model.FormDef)1 IDataReference (org.javarosa.core.model.IDataReference)1 SubmissionProfile (org.javarosa.core.model.SubmissionProfile)1 FormInstance (org.javarosa.core.model.instance.FormInstance)1 Document (org.kxml2.kdom.Document)1 DifferenceResult (org.opendatakit.aggregate.parser.BaseFormParserForJavaRosa.DifferenceResult)1 BadFormDefinition (org.opendatakit.briefcase.util.BadFormDefinition)1 JavaRosaParserWrapper (org.opendatakit.briefcase.util.JavaRosaParserWrapper)1