diff --git a/pom.xml b/pom.xml index a40d16cd..80d23173 100644 --- a/pom.xml +++ b/pom.xml @@ -1,433 +1,440 @@ - - 4.0.0 - - org.sitenv - referenceccdavalidator - 1.0.10 - war - Reference CCDA Validator - - - - 1.7 - UTF-8 - UTF-8 - - - - - mdht-vocab-repo - file://${project.basedir}/mdht-vocab-repo - - - - - - - org.mdht.dependencies - org.eclipse.mdht.emf.runtime - 3.0.0.201612130602 - - - - org.mdht.dependencies - org.openhealthtools.mdht.uml.cda.consol2 - 2.5.22.20161213 - - - - org.mdht.dependencies - org.eclipse.mdht.uml.cda - 3.0.0.201612130602 - - - - org.mdht.dependencies - org.eclipse.mdht.uml.hl7.datatypes - 3.0.0.201612130602 - - - - org.mdht.dependencies - org.eclipse.mdht.uml.hl7.rim - 3.0.0.201612130602 - - - - org.mdht.dependencies - org.eclipse.mdht.uml.hl7.vocab - 3.0.0.201612130602 - - - - - org.mdht.dependencies - lpg.runtime.java - 2.0.17.v201004271640 - - - - org.mdht.dependencies - org.eclipse.emf.common - 2.11.1.v20160208-0816 - - - - org.mdht.dependencies - org.eclipse.emf.ecore - 2.11.2.v20160208-0816 - - - - org.mdht.dependencies - org.eclipse.emf.ecore.xmi - 2.11.1.v20160208-0816 - - - - org.mdht.dependencies - org.eclipse.ocl - 3.5.0.v20150521-1211 - - - - org.mdht.dependencies - org.eclipse.ocl.common - 1.3.0.v20150519-0914 - - - - org.mdht.dependencies - org.eclipse.ocl.ecore - 3.5.0.v20150525-1635 - - - - org.mdht.dependencies - org.eclipse.uml2.common - 2.1.0.v20160201-0816 - - - - org.mdht.dependencies - org.eclipse.uml2.types - 2.0.0.v20160201-0816 - - - - - - - org.sitenv.vocabulary - codevalidator-api - 1.0.4 - - - - - - org.sitenv - contentvalidator-api - 1.0.6 - - - - - - org.springframework - spring-core - - - org.springframework - spring-beans - - - org.springframework - spring-context-support - - - org.springframework - spring-context - - - commons-logging - commons-logging - - - - - - org.springframework - spring-webmvc - - - org.springframework - spring-test - - - io.springfox - springfox-swagger2 - 2.5.0 - - - io.springfox - springfox-swagger-ui - 2.5.0 - - - junit - junit - 3.8.1 - test - - - javax.servlet - javax.servlet-api - 3.1.0 - - - javax.servlet.jsp - jsp-api - 2.0 - provided - - - commons-fileupload - commons-fileupload - 1.3.1 - - - com.fasterxml.jackson.core - jackson-core - 2.5.3 - - - com.fasterxml.jackson.core - jackson-databind - 2.5.3 - - - org.apache.commons - commons-lang3 - 3.4 - - - org.slf4j - jcl-over-slf4j - 1.7.0 - runtime - - - org.slf4j - slf4j-api - 1.7.0 - runtime - - - org.slf4j - slf4j-log4j12 - 1.7.0 - runtime - - - log4j - log4j - 1.2.14 - runtime - - - junit - junit - 4.12 - test - - - - - - org.springframework - spring-framework-bom - 4.2.4.RELEASE - pom - import - - - - - referenceccdaservice - - - org.apache.maven.plugins - maven-eclipse-plugin - 2.8 - - 2.0 - true - true - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - 1.7 - 1.7 - - - - - org.eclipse.jetty - jetty-maven-plugin - 9.2.2.v20140723 - - 3 - -Xmx2024m -Xms2024m -XX:PermSize=256m -XX:MaxPermSize=512m - - /referenceccdaservice - - - - - external.atlassian.jgitflow - jgitflow-maven-plugin - 1.0-m5.1 - - true - true - true - true - - - - org.apache.tomcat.maven - tomcat7-maven-plugin - 2.2 - - ${tomcat-server} - ${tomcat-url} - /referenceccdaservice - true - - - - - - - local - - - - org.apache.maven.plugins - maven-war-plugin - 2.4 - - false - ${project.build.directory}/${project.build.finalName} - - src/main/profiles/local/environment.properties - - - - src/main/webapp - true - - - - - - - - - test - - - - org.apache.maven.plugins - maven-war-plugin - 2.4 - - false - ${project.build.directory}/${project.build.finalName} - - src/main/profiles/test/environment.properties - - - - src/main/webapp - true - - - - - - - - - prod - - true - - - - - org.apache.maven.plugins - maven-war-plugin - 2.4 - - false - ${project.build.directory}/${project.build.finalName} - - src/main/profiles/prod/environment.properties - - - - src/main/webapp - true - - - - - - - - - - distribution - - - - maven-assembly-plugin - 2.5.3 - - - src/main/java/org/sitenv/referenceccda/assembly/referenceccdaapi.xml - - - - - package - - single - - - - - - org.apache.maven.plugins - maven-war-plugin - 2.4 - - false - ${project.build.directory}/${project.build.finalName} - **/context.xml - - - - - - - + + 4.0.0 + + org.sitenv + referenceccdavalidator + 1.0.11 + war + Reference CCDA Validator + + + + 1.7 + UTF-8 + UTF-8 + + + + + + + false + + central + libs-release + http://devsoap.sitenv.org:8081/artifactory/libs-release + + + + snapshots + libs-snapshot + http://devsoap.sitenv.org:8081/artifactory/libs-snapshot + + + + + + + + org.openhealthtools.mdht.cda + org.openhealthtools.mdht.uml.cda.consol2 + 2.6.0.20161229 + + + org.openhealthtools.mdht.cda + org.openhealthtools.mdht.uml.cda.mu2consol + 2.6.0.20161229 + + + + org.eclipse.mdht + org.eclipse.mdht.emf.runtime + 3.0.0-20170103.233449-2 + + + org.eclipse.mdht + org.eclipse.mdht.uml.cda + 3.0.0-20170103.234048-3 + + + org.eclipse.mdht + org.eclipse.mdht.uml.hl7.datatypes + 3.0.0-20170103.234136-3 + + + org.eclipse.mdht + org.eclipse.mdht.uml.hl7.rim + 3.0.0-20170103.234147-3 + + + org.eclipse.mdht + org.eclipse.mdht.uml.hl7.vocab + 3.0.0-20170103.234157-3 + + + + lpg.runtime.java + lpg.runtime.java + 2.0.17.v201004271640 + + + org.eclipse.emf.common + org.eclipse.emf.common + 2.11.1.v20160208-0816 + + + org.eclipse.emf.ecore + org.eclipse.emf.ecore + 2.11.2.v20160208-0816 + + + org.eclipse.emf.ecore.xmi + org.eclipse.emf.ecore.xmi + 2.11.1.v20160208-0816 + + + org.eclipse.ocl + org.eclipse.ocl + 3.5.0.v20150521-1211 + + + org.eclipse.ocl.common + org.eclipse.ocl.common + 1.3.0.v20150519-0914 + + + org.eclipse.ocl.ecore + org.eclipse.ocl.ecore + 3.5.0.v20150525-1635 + + + org.eclipse.uml2.common + org.eclipse.uml2.common + 2.1.0.v20160201-0816 + + + org.eclipse.uml2.types + org.eclipse.uml2.types + 2.0.0.v20160201-0816 + + + + + + org.sitenv.vocabulary + codevalidator-api + 1.0.5 + + + + + + org.sitenv + contentvalidator-api + 1.0.6 + + + + + + org.springframework + spring-core + + + org.springframework + spring-beans + + + org.springframework + spring-context-support + + + org.springframework + spring-context + + + commons-logging + commons-logging + + + + + + org.springframework + spring-webmvc + + + org.springframework + spring-test + + + io.springfox + springfox-swagger2 + 2.5.0 + + + io.springfox + springfox-swagger-ui + 2.5.0 + + + junit + junit + 3.8.1 + test + + + javax.servlet + javax.servlet-api + 3.1.0 + + + javax.servlet.jsp + jsp-api + 2.0 + provided + + + commons-fileupload + commons-fileupload + 1.3.1 + + + com.fasterxml.jackson.core + jackson-core + 2.5.3 + + + com.fasterxml.jackson.core + jackson-databind + 2.5.3 + + + org.apache.commons + commons-lang3 + 3.4 + + + org.slf4j + jcl-over-slf4j + 1.7.0 + runtime + + + org.slf4j + slf4j-api + 1.7.0 + runtime + + + org.slf4j + slf4j-log4j12 + 1.7.0 + runtime + + + log4j + log4j + 1.2.14 + runtime + + + junit + junit + 4.12 + test + + + + + + + org.springframework + spring-framework-bom + 4.2.4.RELEASE + pom + import + + + + + referenceccdaservice + + + org.apache.maven.plugins + maven-eclipse-plugin + 2.8 + + 2.0 + true + true + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.7 + 1.7 + + + + + org.eclipse.jetty + jetty-maven-plugin + 9.2.2.v20140723 + + 3 + -Xmx2024m -Xms2024m -XX:PermSize=256m -XX:MaxPermSize=512m + + /referenceccdaservice + + + + + external.atlassian.jgitflow + jgitflow-maven-plugin + 1.0-m5.1 + + true + true + true + true + + + + org.apache.tomcat.maven + tomcat7-maven-plugin + 2.2 + + ${tomcat-server} + ${tomcat-url} + /referenceccdaservice + true + + + + + + + local + + + + org.apache.maven.plugins + maven-war-plugin + 2.4 + + false + ${project.build.directory}/${project.build.finalName} + + src/main/profiles/local/environment.properties + + + + src/main/webapp + true + + + + + + + + + test + + + + org.apache.maven.plugins + maven-war-plugin + 2.4 + + false + ${project.build.directory}/${project.build.finalName} + + src/main/profiles/test/environment.properties + + + + src/main/webapp + true + + + + + + + + + prod + + true + + + + + org.apache.maven.plugins + maven-war-plugin + 2.4 + + false + ${project.build.directory}/${project.build.finalName} + + src/main/profiles/prod/environment.properties + + + + src/main/webapp + true + + + + + + + + + + distribution + + + + maven-assembly-plugin + 2.5.3 + + + src/main/java/org/sitenv/referenceccda/assembly/referenceccdaapi.xml + + + + + package + + single + + + + + + org.apache.maven.plugins + maven-war-plugin + 2.4 + + false + ${project.build.directory}/${project.build.finalName} + **/context.xml + + + + + + + diff --git a/src/main/java/org/sitenv/referenceccda/services/ReferenceCCDAValidationService.java b/src/main/java/org/sitenv/referenceccda/services/ReferenceCCDAValidationService.java index d3163055..21fad65e 100644 --- a/src/main/java/org/sitenv/referenceccda/services/ReferenceCCDAValidationService.java +++ b/src/main/java/org/sitenv/referenceccda/services/ReferenceCCDAValidationService.java @@ -1,28 +1,45 @@ package org.sitenv.referenceccda.services; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + import org.apache.commons.io.IOUtils; import org.apache.commons.io.input.BOMInputStream; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.log4j.Logger; import org.sitenv.referenceccda.dto.ValidationResultsDto; import org.sitenv.referenceccda.dto.ValidationResultsMetaData; import org.sitenv.referenceccda.validators.RefCCDAValidationResult; import org.sitenv.referenceccda.validators.content.ReferenceContentValidator; +import org.sitenv.referenceccda.validators.schema.CCDATypes; import org.sitenv.referenceccda.validators.schema.ReferenceCCDAValidator; +import org.sitenv.referenceccda.validators.schema.ValidationObjectives; import org.sitenv.referenceccda.validators.vocabulary.VocabularyCCDAValidator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import org.xml.sax.SAXException; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - @Service public class ReferenceCCDAValidationService { + private static Logger logger = Logger.getLogger(ReferenceCCDAValidationService.class); + private ReferenceCCDAValidator referenceCCDAValidator; private VocabularyCCDAValidator vocabularyCCDAValidator; private ReferenceContentValidator goldMatchingValidator; + + private static final String ERROR_GENERAL_PREFIX = "The service has encountered "; + private static final String ERROR_PARSING_PREFIX = ERROR_GENERAL_PREFIX + "an error parsing the document. "; + private static final String ERROR_FOLLOWING_ERROR_POSTFIX = "the following error: "; + private static final String ERROR_IO_EXCEPTION = ERROR_GENERAL_PREFIX + "the following input/output error: "; + private static final String ERROR_CLASS_CAST_EXCEPTION = ERROR_PARSING_PREFIX + + "Please verify the document is valid against schema and " + + "contains a v3 namespace definition: "; + private static final String ERROR_SAX_PARSE_EXCEPTION = ERROR_PARSING_PREFIX + + "Please verify the document does not contain in-line XSL styling and/or address " + ERROR_FOLLOWING_ERROR_POSTFIX; + private static final String ERROR_GENERIC_EXCEPTION = ERROR_GENERAL_PREFIX + ERROR_FOLLOWING_ERROR_POSTFIX; @Autowired public ReferenceCCDAValidationService(ReferenceCCDAValidator referenceCCDAValidator, VocabularyCCDAValidator vocabularyCCDAValidator, @@ -41,18 +58,41 @@ public ValidationResultsDto validateCCDA(String validationObjective, String refe resultsMetaData = buildValidationMedata(validatorResults, validationObjective); resultsMetaData.setCcdaFileName(ccdaFile.getName()); resultsMetaData.setCcdaFileContents(new String(ccdaFile.getBytes())); - } catch (SAXException | IOException e) { - resultsMetaData.setServiceError(true); - resultsMetaData.setServiceErrorMessage(e.getMessage()); - resultsMetaData.setCcdaDocumentType(validationObjective); - } + } catch (IOException ioE) { + processValidateCCDAException(resultsMetaData, + ERROR_IO_EXCEPTION, validationObjective, ioE); + } catch (SAXException saxE) { + processValidateCCDAException(resultsMetaData, + ERROR_SAX_PARSE_EXCEPTION, validationObjective, saxE); + } catch (ClassCastException ccE) { + processValidateCCDAException(resultsMetaData, + ERROR_CLASS_CAST_EXCEPTION, validationObjective, ccE); + } catch (Exception catchAllE) { + processValidateCCDAException(resultsMetaData, + ERROR_GENERIC_EXCEPTION, validationObjective, catchAllE); + } resultsDto.setResultsMetaData(resultsMetaData); resultsDto.setCcdaValidationResults(validatorResults); return resultsDto; } + + private static void processValidateCCDAException(ValidationResultsMetaData resultsMetaData, + String serviceErrorStart, String validationObjective, Exception exception) { + resultsMetaData.setServiceError(true); + if(exception.getMessage() != null) { + String fullError = serviceErrorStart + exception.getMessage(); + logger.error(fullError); + resultsMetaData.setServiceErrorMessage(fullError); + } else { + String fullError = serviceErrorStart + ExceptionUtils.getStackTrace(exception); + logger.error(fullError); + resultsMetaData.setServiceErrorMessage(fullError); + } + resultsMetaData.setCcdaDocumentType(validationObjective); + } private List runValidators(String validationObjective, String referenceFileName, - MultipartFile ccdaFile) throws SAXException { + MultipartFile ccdaFile) throws SAXException, Exception { List validatorResults = new ArrayList<>(); InputStream ccdaFileInputStream = null; try { @@ -60,18 +100,37 @@ private List runValidators(String validationObjective, String ccdaFileContents = IOUtils.toString(new BOMInputStream(ccdaFileInputStream)); List mdhtResults = doMDHTValidation(validationObjective, referenceFileName, ccdaFileContents); - if(mdhtResults != null && !mdhtResults.isEmpty()) + if(mdhtResults != null && !mdhtResults.isEmpty()) { + logger.info("Adding MDHT results"); validatorResults.addAll(mdhtResults); + } - if (!mdhtResultsHaveSchemaError(mdhtResults)) { - List vocabResults = DoVocabularyValidation(validationObjective, referenceFileName, ccdaFileContents); - if(vocabResults != null && !vocabResults.isEmpty()) - validatorResults.addAll(vocabResults); - - List contentResults = doContentValidation(validationObjective, referenceFileName, ccdaFileContents); - if(contentResults != null && !contentResults.isEmpty()) { - validatorResults.addAll(contentResults); + boolean isSchemaErrorInMdhtResults = mdhtResultsHaveSchemaError(mdhtResults); + boolean isObjectiveAllowingVocabularyValidation = objectiveAllowsVocabularyValidation(validationObjective); + if (!isSchemaErrorInMdhtResults && isObjectiveAllowingVocabularyValidation) { + List vocabResults = doVocabularyValidation(validationObjective, referenceFileName, ccdaFileContents); + if(vocabResults != null && !vocabResults.isEmpty()) { + logger.info("Adding Vocabulary results"); + validatorResults.addAll(vocabResults); + } + + if(objectiveAllowsContentValidation(validationObjective)) { + List contentResults = doContentValidation(validationObjective, referenceFileName, ccdaFileContents); + if(contentResults != null && !contentResults.isEmpty()) { + logger.info("Adding Content results"); + validatorResults.addAll(contentResults); + } + } else { + logger.info("Skipping Content validation due to: " + + "validationObjective (" + (validationObjective != null ? validationObjective : "null objective") + + ") is not relevant or valid for Content validation"); } + } else { + String separator = !isObjectiveAllowingVocabularyValidation && isSchemaErrorInMdhtResults ? " and " : ""; + logger.info("Skipping Vocabulary (and thus Content) validation due to: " + + (isObjectiveAllowingVocabularyValidation ? "" : "validationObjective POSTed: " + + (validationObjective != null ? validationObjective : "null objective") + separator) + + (isSchemaErrorInMdhtResults ? "C-CDA Schema error(s) found" : "")); } } catch (IOException e) { throw new RuntimeException("Error getting CCDA contents from provided file", e); @@ -80,7 +139,7 @@ private List runValidators(String validationObjective, } return validatorResults; } - + private boolean mdhtResultsHaveSchemaError(List mdhtResults) { for(RefCCDAValidationResult result : mdhtResults){ if(result.isSchemaError()){ @@ -88,17 +147,31 @@ private boolean mdhtResultsHaveSchemaError(List mdhtRes } } return false; - } - - private ArrayList DoVocabularyValidation(String validationObjective, String referenceFileName, String ccdaFileContents) throws SAXException { - return vocabularyCCDAValidator.validateFile(validationObjective, referenceFileName, ccdaFileContents); + } + + private boolean objectiveAllowsVocabularyValidation(String validationObjective) { + return !validationObjective.equalsIgnoreCase(ValidationObjectives.Sender.C_CDA_IG_ONLY) + && !referenceCCDAValidator.isValidationObjectiveMu2Type() + && !validationObjective.equalsIgnoreCase(CCDATypes.NON_SPECIFIC_CCDA); } + + private boolean objectiveAllowsContentValidation(String validationObjective) { + return ReferenceCCDAValidator.isValidationObjectiveACertainType(validationObjective, + ValidationObjectives.ALL_UNIQUE_CONTENT_ONLY); + } - private List doMDHTValidation(String validationObjective, String referenceFileName, String ccdaFileContents) throws SAXException { + private List doMDHTValidation(String validationObjective, String referenceFileName, String ccdaFileContents) throws SAXException, Exception { + logger.info("Attempting MDHT validation..."); return referenceCCDAValidator.validateFile(validationObjective, referenceFileName, ccdaFileContents); } + + private ArrayList doVocabularyValidation(String validationObjective, String referenceFileName, String ccdaFileContents) throws SAXException { + logger.info("Attempting Vocabulary validation..."); + return vocabularyCCDAValidator.validateFile(validationObjective, referenceFileName, ccdaFileContents); + } private List doContentValidation(String validationObjective, String referenceFileName, String ccdaFileContents) throws SAXException { + logger.info("Attempting Content validation..."); return goldMatchingValidator.validateFile(validationObjective, referenceFileName, ccdaFileContents); } diff --git a/src/main/java/org/sitenv/referenceccda/validators/CCDAValidator.java b/src/main/java/org/sitenv/referenceccda/validators/CCDAValidator.java index de817cf3..d365cbe8 100644 --- a/src/main/java/org/sitenv/referenceccda/validators/CCDAValidator.java +++ b/src/main/java/org/sitenv/referenceccda/validators/CCDAValidator.java @@ -8,5 +8,6 @@ * Created by Brian on 10/20/2015. */ public interface CCDAValidator { - ArrayList validateFile(String validationObjective, String referenceFileName, String ccdaFile) throws SAXException; + ArrayList validateFile(String validationObjective, + String referenceFileName, String ccdaFile) throws SAXException, Exception; } diff --git a/src/main/java/org/sitenv/referenceccda/validators/RefCCDAValidationResult.java b/src/main/java/org/sitenv/referenceccda/validators/RefCCDAValidationResult.java index b2ec4f4f..48acfa98 100644 --- a/src/main/java/org/sitenv/referenceccda/validators/RefCCDAValidationResult.java +++ b/src/main/java/org/sitenv/referenceccda/validators/RefCCDAValidationResult.java @@ -1,6 +1,7 @@ package org.sitenv.referenceccda.validators; import org.sitenv.referenceccda.validators.enums.ValidationResultType; +import org.sitenv.referenceccda.validators.schema.MDHTResultDetails; public class RefCCDAValidationResult { @@ -10,7 +11,9 @@ public class RefCCDAValidationResult { private final String xPath; private final String validatorConfiguredXpath; private final String documentLineNumber; - private final boolean isSchemaError, isDataTypeSchemaError; + + // Only required for MDHT + private final MDHTResultDetails mdhtResultDetails; // Only valid for Vocabulary testing private String actualCode; @@ -28,8 +31,11 @@ private RefCCDAValidationResult(RefCCDAValidationResultBuilder builder){ this.actualCodeSystem = builder.actualCodeSystem; this.actualCodeSystemName = builder.actualCodeSystemName; this.actualDisplayName = builder.actualDisplayName; - this.isSchemaError = builder.isSchemaError; - this.isDataTypeSchemaError = builder.isDataTypeSchemaError; + if(builder.mdhtResultDetails != null) { + this.mdhtResultDetails = builder.mdhtResultDetails; + } else { + this.mdhtResultDetails = new MDHTResultDetails(false, false, false, false); + } } public String getDescription() { @@ -53,12 +59,20 @@ public String getDocumentLineNumber() { } public boolean isSchemaError() { - return isSchemaError; + return mdhtResultDetails.isSchemaError(); } public boolean isDataTypeSchemaError() { - return isDataTypeSchemaError; + return mdhtResultDetails.isDataTypeSchemaError(); } + + public boolean isIGIssue() { + return mdhtResultDetails.isIGIssue(); + } + + public boolean isMUIssue() { + return mdhtResultDetails.isMUIssue(); + } public String getActualCodeSystem() { return actualCodeSystem; @@ -84,7 +98,9 @@ public static class RefCCDAValidationResultBuilder{ private final String xPath; private final String validatorConfiguredXpath; private final String documentLineNumber; - private final boolean isSchemaError, isDataTypeSchemaError; + + // Only required for MDHT + private MDHTResultDetails mdhtResultDetails; // Only valid for Vocabulary testing private String actualCodeSystem; @@ -93,16 +109,17 @@ public static class RefCCDAValidationResultBuilder{ private String actualCodeSystemName; public RefCCDAValidationResultBuilder(String description, String xPath, - String validatorConfiguredXpath, ValidationResultType type, - String documentLineNumber, boolean isSchemaError, - boolean isDataTypeSchemaError) { + String validatorConfiguredXpath, ValidationResultType type, String documentLineNumber) { this.description = description; this.validatorConfiguredXpath = validatorConfiguredXpath; this.type = type; this.xPath = xPath; this.documentLineNumber = documentLineNumber; - this.isSchemaError = isSchemaError; - this.isDataTypeSchemaError = isDataTypeSchemaError; + } + + public RefCCDAValidationResultBuilder mdhtResultDetails(MDHTResultDetails mdhtResultDetails) { + this.mdhtResultDetails = mdhtResultDetails; + return this; } public RefCCDAValidationResultBuilder actualCodeSystem(String actualCodeSystem) { @@ -125,7 +142,7 @@ public RefCCDAValidationResultBuilder actualCodeSystemName(String actualCodeSyst return this; } - public RefCCDAValidationResult build(){ + public RefCCDAValidationResult build() { return new RefCCDAValidationResult(this); } } diff --git a/src/main/java/org/sitenv/referenceccda/validators/content/ReferenceContentValidator.java b/src/main/java/org/sitenv/referenceccda/validators/content/ReferenceContentValidator.java index 9bf64999..e80a8e43 100644 --- a/src/main/java/org/sitenv/referenceccda/validators/content/ReferenceContentValidator.java +++ b/src/main/java/org/sitenv/referenceccda/validators/content/ReferenceContentValidator.java @@ -1,5 +1,8 @@ package org.sitenv.referenceccda.validators.content; +import java.util.ArrayList; +import java.util.List; + import org.sitenv.contentvalidator.dto.ContentValidationResult; import org.sitenv.contentvalidator.service.ContentValidatorService; import org.sitenv.referenceccda.validators.BaseCCDAValidator; @@ -10,9 +13,6 @@ import org.springframework.stereotype.Component; import org.xml.sax.SAXException; -import java.util.ArrayList; -import java.util.List; - /** * Created by Brian on 8/15/2016. @@ -54,8 +54,8 @@ private RefCCDAValidationResult createValidationResult(ContentValidationResult r default: type = ValidationResultType.REF_CCDA_INFO; break; } - - return new RefCCDAValidationResult.RefCCDAValidationResultBuilder(result.getMessage(), null, null, type, "0", false, false) + + return new RefCCDAValidationResult.RefCCDAValidationResultBuilder(result.getMessage(), null, null, type, "0") .build(); } } diff --git a/src/main/java/org/sitenv/referenceccda/validators/schema/CCDATypes.java b/src/main/java/org/sitenv/referenceccda/validators/schema/CCDATypes.java new file mode 100644 index 00000000..d06be7df --- /dev/null +++ b/src/main/java/org/sitenv/referenceccda/validators/schema/CCDATypes.java @@ -0,0 +1,54 @@ +package org.sitenv.referenceccda.validators.schema; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public final class CCDATypes { + // higher level validation versions - not an objective + public static final String CCDAR21_OR_CCDAR11 = "C-CDA R2.1 or R1.1 Document"; + public static final String CCDAR11_MU2 = "C-CDA R1.1 MU2 Document"; + + /// all below here are an actual validationObjective + // generic CCDA base level + public static final String NON_SPECIFIC_CCDA = "NonSpecificCCDA"; + public static final String NON_SPECIFIC_CCDAR2 = "NonSpecificCCDAR2"; + public static final List NON_SPECIFIC_CCDA_TYPES = new ArrayList( + Arrays.asList(NON_SPECIFIC_CCDA, NON_SPECIFIC_CCDAR2)); + + // most common mu2 + public static final String TRANSITIONS_OF_CARE_AMBULATORY_SUMMARY = "TransitionsOfCareAmbulatorySummary"; + // other mu2 + public static final String CLINICAL_OFFICE_VISIT_SUMMARY = "ClinicalOfficeVisitSummary"; + public static final String TRANSITIONS_OF_CARE_INPATIENT_SUMMARY = "TransitionsOfCareInpatientSummary"; + public static final String VDT_AMBULATORY_SUMMARY = "VDTAmbulatorySummary"; + public static final String VDT_INPATIENT_SUMMARY = "VDTInpatientSummary"; + public static final List MU2_TYPES = new ArrayList( + Arrays.asList(TRANSITIONS_OF_CARE_AMBULATORY_SUMMARY, + CLINICAL_OFFICE_VISIT_SUMMARY, + TRANSITIONS_OF_CARE_INPATIENT_SUMMARY, + VDT_AMBULATORY_SUMMARY, VDT_INPATIENT_SUMMARY)); + + // CCDA document level (non-mu2) + public static final String CONSULTATION_NOTE = "ConsultationNote"; + public static final String CONTINUITY_OF_CARE_DOCUMENT = "ContinuityOfCareDocument"; + public static final String DIAGNOSTIC_IMAGING_REPORT = "DiagnosticImagingReport"; + public static final String DISCHARGE_SUMMARY = "DischargeSummary"; + public static final String HISTORY_AND_PHYSICAL_NOTE = "HistoryAndPhysicalNote"; + public static final String OPERATIVE_NOTE = "OperativeNote"; + public static final String PROCEDURE_NOTE = "ProcedureNote"; + public static final String PROGRESS_NOTE = "ProgressNote"; + public static final String UNSTRUCTURED_DOCUMENT = "UnstructuredDocument"; + + public static String getTypes() { + StringBuffer sb = new StringBuffer(); + ValidationObjectives.appendObjectivesData(NON_SPECIFIC_CCDA_TYPES, "LEGACY (same result as 'C-CDA_IG_Plus_Vocab')", sb); + sb.append(" "); + ValidationObjectives.appendObjectivesData(MU2_TYPES, "C-CDA R1.1 WITH MU2", sb); + return sb.toString(); + } + + public static void main(String[] args) { + System.out.println(CCDATypes.getTypes()); + } +} diff --git a/src/main/java/org/sitenv/referenceccda/validators/schema/MDHTResultDetails.java b/src/main/java/org/sitenv/referenceccda/validators/schema/MDHTResultDetails.java new file mode 100644 index 00000000..60a3670f --- /dev/null +++ b/src/main/java/org/sitenv/referenceccda/validators/schema/MDHTResultDetails.java @@ -0,0 +1,45 @@ +package org.sitenv.referenceccda.validators.schema; + +public class MDHTResultDetails { + private boolean isSchemaError, isDataTypeSchemaError, isIGIssue, isMUIssue; + + public MDHTResultDetails(boolean isSchemaError, boolean isDataTypeSchemaError, + boolean isIGIssue, boolean isMUIssue) { + this.isSchemaError = isSchemaError; + this.isDataTypeSchemaError = isDataTypeSchemaError; + this.isIGIssue = isIGIssue; + this.isMUIssue = isMUIssue; + } + + public boolean isSchemaError() { + return isSchemaError; + } + + public void setSchemaError(boolean isSchemaError) { + this.isSchemaError = isSchemaError; + } + + public boolean isDataTypeSchemaError() { + return isDataTypeSchemaError; + } + + public void setDataTypeSchemaError(boolean isDataTypeSchemaError) { + this.isDataTypeSchemaError = isDataTypeSchemaError; + } + + public boolean isIGIssue() { + return isIGIssue; + } + + public void setIGIssue(boolean isIGIssue) { + this.isIGIssue = isIGIssue; + } + + public boolean isMUIssue() { + return isMUIssue; + } + + public void setMUIssue(boolean isMUIssue) { + this.isMUIssue = isMUIssue; + } +} diff --git a/src/main/java/org/sitenv/referenceccda/validators/schema/ReferenceCCDAValidator.java b/src/main/java/org/sitenv/referenceccda/validators/schema/ReferenceCCDAValidator.java index b4885f5b..762720f5 100644 --- a/src/main/java/org/sitenv/referenceccda/validators/schema/ReferenceCCDAValidator.java +++ b/src/main/java/org/sitenv/referenceccda/validators/schema/ReferenceCCDAValidator.java @@ -1,7 +1,16 @@ package org.sitenv.referenceccda.validators.schema; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; import org.eclipse.emf.common.util.Diagnostic; +import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.mdht.uml.cda.DocumentRoot; @@ -9,6 +18,7 @@ import org.eclipse.mdht.uml.cda.util.CDAUtil; import org.eclipse.mdht.uml.cda.util.ValidationResult; import org.openhealthtools.mdht.uml.cda.consol.ConsolPackage; +import org.openhealthtools.mdht.uml.cda.mu2consol.Mu2consolPackage; import org.sitenv.referenceccda.validators.BaseCCDAValidator; import org.sitenv.referenceccda.validators.CCDAValidator; import org.sitenv.referenceccda.validators.RefCCDAValidationResult; @@ -17,27 +27,29 @@ import org.springframework.stereotype.Component; import org.xml.sax.SAXException; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - @Component public class ReferenceCCDAValidator extends BaseCCDAValidator implements CCDAValidator { + private static Logger logger = Logger.getLogger(ReferenceCCDAValidator.class); + + private static final String IG_ISSUE_ID = "a.consol", MU_ISSUE_ID = "a.mu2con"; + private boolean isValidationObjectiveMu2Type = false; + + public boolean isValidationObjectiveMu2Type() { + return isValidationObjectiveMu2Type; + } public ArrayList validateFile(String validationObjective, - String referenceFileName, String ccdaFile) throws SAXException { + String referenceFileName, String ccdaFile) throws SAXException, Exception { final XPathIndexer xpathIndexer = new XPathIndexer(); ValidationResult result = new ValidationResult(); InputStream in = null; - createValidationResultObjectToCollectDiagnosticsProducedDuringValidation(); trackXPathsInXML(xpathIndexer, ccdaFile); try { in = IOUtils.toInputStream(ccdaFile, "UTF-8"); - CDAUtil.load(in, result); - } catch (Exception e) { + validateDocumentByTypeUsingMDHTApi(in, validationObjective, result); + } catch (IOException e) { e.printStackTrace(); - }finally{ + } finally { if (in != null) { try { in.close(); @@ -46,55 +58,158 @@ public ArrayList validateFile(String validationObjectiv } } } + if(result.getAllDiagnostics().isEmpty()) { + logAndThrowException("The MDHT ValidationResult object was not populated for an unknown reason."); + } + logger.info("Processing and returning MDHT validation results"); return processValidationResults(xpathIndexer, result); } - private void createValidationResultObjectToCollectDiagnosticsProducedDuringValidation() { - ConsolPackage.eINSTANCE.eClass(); + private void validateDocumentByTypeUsingMDHTApi(InputStream in, String validationObjective, + ValidationResult result) throws Exception { + if(StringUtils.isEmpty(validationObjective)) { + logAndThrowException("The validationObjective given is " + (validationObjective == null ? "null" : "empty"), + "The validationObjective given was null or empty. Please try one of the following valid Strings instead: " + + ValidationObjectives.getObjectives() + " " + CCDATypes.getTypes()); + } + + String mdhtValidationObjective = mapMdhtValidationObjective(validationObjective); + logger.info("Mapped mdhtValidationObjective: " + (mdhtValidationObjective != null ? mdhtValidationObjective : "null objective")); + if(mdhtValidationObjective != null) { + //populate the field for reuse + isValidationObjectiveMu2Type = isValidationObjectiveMu2Type(mdhtValidationObjective); + if (isValidationObjectiveCCDAType(mdhtValidationObjective)) { + Mu2consolPackage.eINSTANCE.unload(); + ConsolPackage.eINSTANCE.eClass(); + logger.info("Loading mdhtValidationObjective: " + mdhtValidationObjective + + " mapped from valdationObjective: " + validationObjective); + CDAUtil.load(in, result); + } else if (isValidationObjectiveMu2Type) { + Mu2consolPackage.eINSTANCE.reload(); + Mu2consolPackage.eINSTANCE.eClass(); + EClass docType = null; + if (mdhtValidationObjective.equalsIgnoreCase(CCDATypes.CLINICAL_OFFICE_VISIT_SUMMARY)) { + docType = Mu2consolPackage.eINSTANCE.getClinicalOfficeVisitSummary(); + } else if (mdhtValidationObjective.equalsIgnoreCase(CCDATypes.TRANSITIONS_OF_CARE_AMBULATORY_SUMMARY)) { + docType = Mu2consolPackage.eINSTANCE.getTransitionOfCareAmbulatorySummary(); + } else if (mdhtValidationObjective.equalsIgnoreCase(CCDATypes.TRANSITIONS_OF_CARE_INPATIENT_SUMMARY)) { + docType = Mu2consolPackage.eINSTANCE.getTransitionOfCareInpatientSummary(); + } else if (mdhtValidationObjective.equalsIgnoreCase(CCDATypes.VDT_AMBULATORY_SUMMARY)) { + docType = Mu2consolPackage.eINSTANCE.getVDTAmbulatorySummary(); + } else if (mdhtValidationObjective.equalsIgnoreCase(CCDATypes.VDT_INPATIENT_SUMMARY)) { + docType = Mu2consolPackage.eINSTANCE.getVDTInpatientSummary(); + } + boolean isDocTypeNull = docType == null || docType.getName() == null; + logger.info("Loading mdhtValidationObjective: " + mdhtValidationObjective + + " as MU2 docType: " + (!isDocTypeNull ? docType : "null docType") + + " mapped from valdationObjective: " + validationObjective); + if(!isDocTypeNull) { + CDAUtil.loadAs(in, docType, result); + } else { + logAndThrowException("docType == null", "The MU2 docType EClass could not be assigned " + + "from mdhtValidationObjective: " + mdhtValidationObjective); + } + } + } else { + logAndThrowException("The validationObjective given is invalid", + "The validationObjective given was invalid. Please try one of the following valid Strings instead: " + + ValidationObjectives.getObjectives() + " " + CCDATypes.getTypes()); + } + } + + private static String mapMdhtValidationObjective(String validationObjectivePOSTed) throws Exception { + if (isValidationObjectiveACertainType(validationObjectivePOSTed, CCDATypes.NON_SPECIFIC_CCDA_TYPES) || + isValidationObjectiveACertainType(validationObjectivePOSTed, CCDATypes.MU2_TYPES)) { + // we already have a *specific* MDHT objective (it was sent directly so no re-mapping required) + return validationObjectivePOSTed; + } else if (isValidationObjectiveACertainType(validationObjectivePOSTed, ValidationObjectives.ALL_UNIQUE)) { + // convert to a *generic* MDHT objective (runs R2.1 or R1.1 MDHT validation using the consol2 model) + return CCDATypes.NON_SPECIFIC_CCDAR2; + } + logger.warn("An invalid validationObjective was POSTed"); + return null; + } + + private static void logAndThrowException(String dualErrorMessage) throws Exception { + logAndThrowException(dualErrorMessage, dualErrorMessage); + } + + private static void logAndThrowException(String internalErrorMessage, String userErrorMessage) throws Exception { + logger.error(internalErrorMessage); + throw new Exception(userErrorMessage); + } + + public static boolean isValidationObjectiveACertainType(String validationObjective, Collection types) { + for(String type: types) { + if(validationObjective.equalsIgnoreCase(type)) { + return true; + } + } + return false; + } + + private static boolean isValidationObjectiveMu2Type(String validationObjective) { + return isValidationObjectiveACertainType(validationObjective, CCDATypes.MU2_TYPES); + } + + private static boolean isValidationObjectiveCCDAType(String validationObjective) { + return isValidationObjectiveACertainType(validationObjective, CCDATypes.NON_SPECIFIC_CCDA_TYPES); } private ArrayList processValidationResults(final XPathIndexer xpathIndexer, ValidationResult result) { ArrayList results = new ArrayList(); - //CCDAIssueStates.resetHasSchemaError(); - for (Diagnostic diagnostic : result.getErrorDiagnostics()) { - results.add(buildValidationResult(diagnostic, xpathIndexer, ValidationResultType.CCDA_MDHT_CONFORMANCE_ERROR)); - } - - for (Diagnostic diagnostic : result.getWarningDiagnostics()) { - results.add(buildValidationResult(diagnostic, xpathIndexer, ValidationResultType.CCDA_MDHT_CONFORMANCE_WARN)); - } - - for (Diagnostic diagnostic : result.getInfoDiagnostics()) { - results.add(buildValidationResult(diagnostic, xpathIndexer, ValidationResultType.CCDA_MDHT_CONFORMANCE_INFO)); - } + addValidationResults(results, ValidationResultType.CCDA_MDHT_CONFORMANCE_ERROR, result.getErrorDiagnostics(), xpathIndexer); + addValidationResults(results, ValidationResultType.CCDA_MDHT_CONFORMANCE_WARN, result.getWarningDiagnostics(), xpathIndexer); + addValidationResults(results, ValidationResultType.CCDA_MDHT_CONFORMANCE_INFO, result.getInfoDiagnostics(), xpathIndexer); return results; } + + private void addValidationResults(ArrayList results, ValidationResultType currentValidationResultType, + List diagnosticsOfCurrentSeverity, final XPathIndexer xpathIndexer) { + for (Diagnostic diagnostic : diagnosticsOfCurrentSeverity) { + results.add(buildValidationResult(diagnostic, xpathIndexer, currentValidationResultType)); + } + } private RefCCDAValidationResult buildValidationResult(Diagnostic diagnostic, XPathIndexer xPathIndexer, ValidationResultType resultType) { - boolean isResultIGIssue = false; - boolean isResultMUIssue = false; - boolean isResultSchemaError = false; - boolean isResultDataTypeSchemaError = false; - boolean isDocumentSchemaError = false; CDADiagnostic diag = new CDADiagnostic(diagnostic); String lineNumber = getLineNumberInXMLUsingXpath(xPathIndexer, diagnostic); - if(resultType == ValidationResultType.CCDA_MDHT_CONFORMANCE_ERROR) { - if (diag.getSource() != null) { - if (diag.getSource().contains("a.consol")) { - isResultIGIssue = true; + MDHTResultDetails mdhtResultDetails = populateMDHTResultDetails(diag, resultType); + return createNewValidationResult(diag, resultType, lineNumber, mdhtResultDetails); + } + + private MDHTResultDetails populateMDHTResultDetails(CDADiagnostic diag, ValidationResultType resultType) { + MDHTResultDetails mdhtResultDetails = new MDHTResultDetails(false, false, false, false); + if (diag.getSource() != null) { + boolean isIGIssue = diag.getSource().contains(IG_ISSUE_ID); + boolean isMUIssue = isValidationObjectiveMu2Type ? diag.getSource().contains(MU_ISSUE_ID) : false; + //IG/MU2 - all severities + if(resultType == ValidationResultType.CCDA_MDHT_CONFORMANCE_ERROR || + resultType == ValidationResultType.CCDA_MDHT_CONFORMANCE_WARN || + resultType == ValidationResultType.CCDA_MDHT_CONFORMANCE_INFO) { + if (isIGIssue) { + mdhtResultDetails.setIGIssue(true); + return mdhtResultDetails; + } else if (isMUIssue) { + mdhtResultDetails.setMUIssue(true); + return mdhtResultDetails; } else { - // javax.xml.validation.Validator, org.eclipse.emf.ecore, etc. - isResultSchemaError = true; - if (diag.getPath() != null && diag.getCode() > 0) { - // org.eclipse.emf.ecore, etc. - isResultDataTypeSchemaError = true; - } + //schema - errors only + if(resultType == ValidationResultType.CCDA_MDHT_CONFORMANCE_ERROR) { + // javax.xml.validation.Validator, org.eclipse.emf.ecore, etc. + mdhtResultDetails.setSchemaError(true); + if (diag.getPath() != null && diag.getCode() > 0) { + // org.eclipse.emf.ecore, etc. + mdhtResultDetails.setDataTypeSchemaError(true); + return mdhtResultDetails; + } + } } } } - return createNewValidationResult(diag, resultType, lineNumber, isResultSchemaError, isResultDataTypeSchemaError); + return mdhtResultDetails; } private String getLineNumberInXMLUsingXpath(final XPathIndexer xpathIndexer, Diagnostic diagnostic) { @@ -126,10 +241,10 @@ public String getPath(EObject eObject) { } private RefCCDAValidationResult createNewValidationResult(CDADiagnostic cdaDiag, ValidationResultType resultType, - String resultLineNumber, boolean isResultSchemaError, boolean isResultDataTypeSchemaError) { + String resultLineNumber, MDHTResultDetails mdhtResultDetails) { return new RefCCDAValidationResult.RefCCDAValidationResultBuilder( - cdaDiag.getMessage(), cdaDiag.getPath(), null, resultType, - resultLineNumber, isResultSchemaError, - isResultDataTypeSchemaError).build(); + cdaDiag.getMessage(), cdaDiag.getPath(), null, resultType, resultLineNumber) + .mdhtResultDetails(mdhtResultDetails) + .build(); } } diff --git a/src/main/java/org/sitenv/referenceccda/validators/schema/ValidationObjectives.java b/src/main/java/org/sitenv/referenceccda/validators/schema/ValidationObjectives.java new file mode 100644 index 00000000..0c78f0cf --- /dev/null +++ b/src/main/java/org/sitenv/referenceccda/validators/schema/ValidationObjectives.java @@ -0,0 +1,114 @@ +package org.sitenv.referenceccda.validators.schema; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public final class ValidationObjectives { + public static final List ALL = new ArrayList(); + public static final Set ALL_UNIQUE = new HashSet(); + public static final Set ALL_UNIQUE_CONTENT_ONLY = new HashSet(); + static { + ALL.addAll(Sender.OBJECTIVES); + ALL.addAll(Receiver.OBJECTIVES); + ALL_UNIQUE.addAll(ALL); + ALL_UNIQUE_CONTENT_ONLY.addAll(ALL_UNIQUE); + ALL_UNIQUE_CONTENT_ONLY.removeAll(Arrays.asList(Sender.C_CDA_IG_ONLY, Sender.C_CDA_IG_PLUS_VOCAB)); + } + + public static final List CURRENTLY_PROCESSED_BY_CONTENT_VALIDATOR = new ArrayList(Arrays.asList( + Sender.B1_TOC_AMB_170_315, Sender.B1_TOC_INP_170_315, + Sender.B2_CIRI_AMB_170_315, Sender.B2_CIRI_INP_170_315, + Sender.B4_CCDS_AMB_170_315, Sender.B4_CCDS_INP_170_315, + Sender.B6_DE_AMB_170_315, Sender.B6_DE_INP_170_315, + Sender.B9_CP_AMB_170_315, Sender.B9_CP_INP_170_315, + Sender.E1_VDT_AMB_170_315, Sender.E1_VDT_INP_170_315, + Sender.G9_APIACCESS_AMB_170_315, Sender.G9_APIACCESS_INP_170_315)); + + public static class Sender { + public static final String B1_TOC_AMB_170_315 = "170.315_b1_ToC_Amb"; + public static final String B1_TOC_INP_170_315 = "170.315_b1_ToC_Inp"; + public static final String B2_CIRI_AMB_170_315 = "170.315_b2_CIRI_Amb"; + public static final String B2_CIRI_INP_170_315 = "170.315_b2_CIRI_Inp"; + public static final String B4_CCDS_AMB_170_315 = "170.315_b4_CCDS_Amb"; + public static final String B4_CCDS_INP_170_315 = "170.315_b4_CCDS_Inp"; + public static final String B6_DE_AMB_170_315 = "170.315_b6_DE_Amb"; + public static final String B6_DE_INP_170_315 = "170.315_b6_DE_Inp"; + public static final String B7_DS4P_AMB_170_315 = "170.315_b7_DS4P_Amb"; + public static final String B7_DS4P_INP_170_315 = "170.315_b7_DS4P_Inp"; + public static final String B9_CP_AMB_170_315 = "170.315_b9_CP_Amb"; + public static final String B9_CP_INP_170_315 = "170.315_b9_CP_Inp"; + public static final String E1_VDT_AMB_170_315 = "170.315_e1_VDT_Amb"; + public static final String E1_VDT_INP_170_315 = "170.315_e1_VDT_Inp"; + public static final String G9_APIACCESS_AMB_170_315 = "170.315_g9_APIAccess_Amb"; + public static final String G9_APIACCESS_INP_170_315 = "170.315_g9_APIAccess_Inp"; + public static final String C_CDA_IG_ONLY = "C-CDA_IG_Only"; + public static final String C_CDA_IG_PLUS_VOCAB = "C-CDA_IG_Plus_Vocab"; + public static final String GOLD_SAMPLES_FOR_PRACTICE = "Gold_Samples_For_Practice"; + public static final List OBJECTIVES = new ArrayList( + Arrays.asList(B1_TOC_AMB_170_315, B1_TOC_INP_170_315, + B2_CIRI_AMB_170_315, B2_CIRI_INP_170_315, + B4_CCDS_AMB_170_315, B4_CCDS_INP_170_315, + B6_DE_AMB_170_315, B6_DE_INP_170_315, + B7_DS4P_AMB_170_315, B7_DS4P_INP_170_315, + B9_CP_AMB_170_315, B9_CP_INP_170_315, + E1_VDT_AMB_170_315, E1_VDT_INP_170_315, + G9_APIACCESS_AMB_170_315, G9_APIACCESS_INP_170_315, + C_CDA_IG_ONLY, C_CDA_IG_PLUS_VOCAB, + GOLD_SAMPLES_FOR_PRACTICE)); + } + + public static class Receiver { + public static final String B1_TOC_AMB_170_315 = Sender.B1_TOC_AMB_170_315; + public static final String B1_TOC_INP_170_315 = Sender.B1_TOC_INP_170_315; + public static final String B2_CIRI_AMB_170_315 = Sender.B2_CIRI_AMB_170_315; + public static final String B2_CIRI_INP_170_315 = Sender.B2_CIRI_INP_170_315; + public static final String B5_CCDS_AMB_170_315 = "170.315_b5_CCDS_Amb"; + public static final String B5_CCDS_INP_170_315 = "170.315_b5_CCDS_Inp"; + public static final String B8_DS4P_AMB_170_315 = "170.315_b8_DS4P_Amb"; + public static final String B8_DS4P_INP_170_315 = "170.315_b8_DS4P_Inp"; + public static final String B9_CP_AMB_170_315 = Sender.B9_CP_AMB_170_315; + public static final String B9_CP_INP_170_315 = Sender.B9_CP_INP_170_315; + public static final String NEGATIVE_TESTING_CCDS = "NegativeTesting_CCDS"; + public static final String NEGATIVE_TESTING_CAREPLAN = "NegativeTesting_CarePlan"; + public static final List OBJECTIVES = new ArrayList( + Arrays.asList(B1_TOC_AMB_170_315, B1_TOC_INP_170_315, + B2_CIRI_AMB_170_315, B2_CIRI_INP_170_315, + B5_CCDS_AMB_170_315, B5_CCDS_INP_170_315, + B8_DS4P_AMB_170_315, B8_DS4P_INP_170_315, + B9_CP_AMB_170_315, B9_CP_INP_170_315, + NEGATIVE_TESTING_CCDS, NEGATIVE_TESTING_CAREPLAN)); + } + + public static String getObjectives() { + StringBuffer sb = new StringBuffer(); + appendObjectivesData(Sender.OBJECTIVES, "SENDER", sb); + sb.append(" "); + appendObjectivesData(Receiver.OBJECTIVES, "RECEIVER", sb); + return sb.toString(); + } + + protected static void appendObjectivesData(List objectives, String parentClassName, StringBuffer sb) { + sb.append(parentClassName + " options: "); + for (int i = 0; i < objectives.size(); i++) { + sb.append(objectives.get(i) + + (i == objectives.size() - 1 ? "" : ", ")); + } + } + + public static void main(String[] args) { + System.out.println("ValidationObjectives.getObjectives():" + System.lineSeparator() + "-" + ValidationObjectives.getObjectives()); + printObjectivesHelperForDebugging(ALL, "ALL"); + printObjectivesHelperForDebugging(ALL_UNIQUE, "ALL_UNIQUE"); + printObjectivesHelperForDebugging(ALL_UNIQUE_CONTENT_ONLY, "ALL_UNIQUE_CONTENT_ONLY"); + printObjectivesHelperForDebugging(CURRENTLY_PROCESSED_BY_CONTENT_VALIDATOR, "CURRENTLY_PROCESSED_BY_CONTENT_VALIDATOR"); + } + + private static void printObjectivesHelperForDebugging(Collection collection, String description) { + System.out.println(description + ":" + System.lineSeparator() + "-Size: " + collection.size() + System.lineSeparator() + + "-Content" + collection); + } +} diff --git a/src/main/java/org/sitenv/referenceccda/validators/vocabulary/VocabularyCCDAValidator.java b/src/main/java/org/sitenv/referenceccda/validators/vocabulary/VocabularyCCDAValidator.java index b828f191..1c609f80 100644 --- a/src/main/java/org/sitenv/referenceccda/validators/vocabulary/VocabularyCCDAValidator.java +++ b/src/main/java/org/sitenv/referenceccda/validators/vocabulary/VocabularyCCDAValidator.java @@ -1,5 +1,9 @@ package org.sitenv.referenceccda.validators.vocabulary; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + import org.apache.commons.io.IOUtils; import org.sitenv.referenceccda.validators.BaseCCDAValidator; import org.sitenv.referenceccda.validators.CCDAValidator; @@ -13,10 +17,6 @@ import org.springframework.stereotype.Component; import org.xml.sax.SAXException; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - @Component public class VocabularyCCDAValidator extends BaseCCDAValidator implements CCDAValidator { @Value("${referenceccda.configFile}") @@ -63,7 +63,9 @@ private RefCCDAValidationResult createValidationResult(VocabularyValidationResul } String lineNumber = getLineNumberInXMLUsingXpath(xpathIndexer, result.getNodeValidationResult().getValidatedDocumentXpathExpression()); - return new RefCCDAValidationResult.RefCCDAValidationResultBuilder(result.getMessage(), result.getNodeValidationResult().getValidatedDocumentXpathExpression(), result.getNodeValidationResult().getConfiguredXpathExpression(), type, lineNumber, false, false) + return new RefCCDAValidationResult.RefCCDAValidationResultBuilder(result.getMessage(), + result.getNodeValidationResult().getValidatedDocumentXpathExpression(), + result.getNodeValidationResult().getConfiguredXpathExpression(), type, lineNumber) .actualCode(result.getNodeValidationResult().getRequestedCode()) .actualCodeSystem(result.getNodeValidationResult().getRequestedCodeSystem()) .actualCodeSystemName(result.getNodeValidationResult().getRequestedCodeSystemName()) diff --git a/src/test/java/RefCCDATest.java b/src/test/java/RefCCDATest.java index 1b4be90c..d7f7ddc6 100644 --- a/src/test/java/RefCCDATest.java +++ b/src/test/java/RefCCDATest.java @@ -1,85 +1,130 @@ -import org.junit.Test; -import org.sitenv.referenceccda.validators.RefCCDAValidationResult; -import org.sitenv.referenceccda.validators.enums.ValidationResultType; -import org.sitenv.referenceccda.validators.schema.ReferenceCCDAValidator; -import org.xml.sax.SAXException; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.sitenv.contentvalidator.service.ContentValidatorService; +import org.sitenv.referenceccda.dto.ValidationResultsDto; +import org.sitenv.referenceccda.services.ReferenceCCDAValidationService; +import org.sitenv.referenceccda.validators.RefCCDAValidationResult; +import org.sitenv.referenceccda.validators.content.ReferenceContentValidator; +import org.sitenv.referenceccda.validators.enums.ValidationResultType; +import org.sitenv.referenceccda.validators.schema.CCDATypes; +import org.sitenv.referenceccda.validators.schema.ReferenceCCDAValidator; +import org.sitenv.referenceccda.validators.schema.ValidationObjectives; +import org.sitenv.referenceccda.validators.vocabulary.VocabularyCCDAValidator; +import org.sitenv.vocabularies.validation.services.VocabularyValidationService; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; +import org.xml.sax.SAXException; public class RefCCDATest { - private static final boolean SHOW_ERRORS_ONLY = true; - private static final int LAST_SCHEMA_TEST_AND_NO_SCHEMA_ERROR_INDEX = 2; + private static final boolean LOG_RESULTS_TO_CONSOLE = false; + + private static final boolean SHOW_ERRORS_ONLY = false; + private static final int HAS_SCHEMA_ERROR_INDEX = 1; + private static final int LAST_SCHEMA_TEST_AND_NO_SCHEMA_ERROR_INDEX = 2; + private static final int INVALID_SNIPPET_ONLY_INDEX = 3; + private static final int NON_CCDA_XML_HTML_FILE_WITH_XML_EXTENSION_INDEX = 4; + private static final int BLANK_EMPTY_DOCUMENT_INDEX = 5; + private static final int HAS_4_POSSIBLE_CONSOL_AND_1_POSSIBLE_MU2_ERROR = 6; - // it is fine to add documents to the end of this if desired but do not - // alter indexes 0, 1, or 2 + // feel free to add docs to the end but don't alter existing data + // - the same sample is referenced twice due to a loop test private static URI[] CCDA_FILES = new URI[0]; - static { try { - CCDA_FILES = new URI[]{ - RefCCDATest.class.getResource("/Sample.xml").toURI(), - RefCCDATest.class.getResource("/Sample_addSchemaErrors.xml").toURI(), - RefCCDATest.class.getResource("/Sample.xml").toURI()}; + CCDA_FILES = new URI[] { + RefCCDATest.class.getResource("/Sample.xml").toURI(), + RefCCDATest.class + .getResource("/Sample_addSchemaErrors.xml").toURI(), + RefCCDATest.class.getResource("/Sample.xml").toURI(), + RefCCDATest.class.getResource( + "/Sample_invalid-SnippetOnly.xml").toURI(), + RefCCDATest.class.getResource("/Sample_basicHTML.xml") + .toURI(), + RefCCDATest.class.getResource( + "/Sample_blank_Empty_Document.xml").toURI(), + RefCCDATest.class.getResource( + "/Sample_CCDA_CCD_b1_Ambulatory_v2.xml").toURI()}; } catch (URISyntaxException e) { - e.printStackTrace(); + if(LOG_RESULTS_TO_CONSOLE) e.printStackTrace(); } } @Test public void stringConversionAndResultsSizeTest() { String ccdaFileAsString = convertCCDAFileToString(CCDA_FILES[LAST_SCHEMA_TEST_AND_NO_SCHEMA_ERROR_INDEX]); - System.out.println("ccdaFileAsString: " + ccdaFileAsString); + println("ccdaFileAsString: " + ccdaFileAsString); assertFalse( "The C-CDA file String conversion failed as no data was captured", ccdaFileAsString.isEmpty()); ArrayList results = validateDocumentAndReturnResults(ccdaFileAsString); ; - System.out.println("No of Entries = " + results.size()); + println("No of Entries = " + results.size()); assertFalse("No results were returned", results.isEmpty()); - System.out - .println("***************** No Exceptions were thrown during the test******************" + println("***************** No Exceptions were thrown during the test******************" + System.lineSeparator() + System.lineSeparator()); } @Test - public void hasSchemaErrorTest() { - ArrayList results = validateDocumentAndReturnResults(convertCCDAFileToString(CCDA_FILES[HAS_SCHEMA_ERROR_INDEX])); - // global result + public void hasSchemaErrorsAndDatatypeSchemaErrorTest() { + ArrayList results = + validateDocumentAndReturnResults(convertCCDAFileToString(CCDA_FILES[HAS_SCHEMA_ERROR_INDEX])); + println("global result"); assertTrue( "The document has a schema error yet the flag is set to false", mdhtResultsHaveSchemaError(results)); - // and for sanity, check the single results as well - boolean schemaErrorInSingleResultFound = false; - for (RefCCDAValidationResult result : results) - if (result.isSchemaError()) + println("and for sanity, check the single results as well"); + printResults(getMDHTErrorsFromResults(results)); + boolean schemaErrorInSingleResultFound = false, expectedDataTypeSchemaErrorInResultsFound = false; + String expectedDatatypeSchemaErrorPrefix = "The feature 'author' of"; + for (RefCCDAValidationResult result : results) { + if (result.isSchemaError()) { schemaErrorInSingleResultFound = true; - assertTrue( - "The document has at least one schema error but no single result flagged it as such", + final String msgPrefix = "A schema error cannot also be an "; + assertFalse(msgPrefix + "IG Issue", result.isIGIssue()); + assertFalse(msgPrefix + "MU2 Issue", result.isMUIssue()); + } + if(result.getDescription().contains( + expectedDatatypeSchemaErrorPrefix)) { + expectedDataTypeSchemaErrorInResultsFound = true; + } + } + assertTrue("The document has at least one schema error but no single result flagged it as such", schemaErrorInSingleResultFound); + assertTrue("The document is expected to return has the following specific data type schema error (prefix) :" + + expectedDatatypeSchemaErrorPrefix, expectedDataTypeSchemaErrorInResultsFound); } @Test public void doesNotHaveSchemaErrorTest() { - ArrayList results = validateDocumentAndReturnResults(convertCCDAFileToString(CCDA_FILES[LAST_SCHEMA_TEST_AND_NO_SCHEMA_ERROR_INDEX])); - // global result + ArrayList results = + validateDocumentAndReturnResults(convertCCDAFileToString(CCDA_FILES[LAST_SCHEMA_TEST_AND_NO_SCHEMA_ERROR_INDEX])); + println("global result"); assertFalse( "The document does not have schema error yet the flag is set to true", mdhtResultsHaveSchemaError(results)); - // and for sanity, check the single results as well + println("and for sanity, check the single results as well"); boolean schemaErrorInSingleResultFound = false; + printResults(getMDHTErrorsFromResults(results)); for (RefCCDAValidationResult result : results) if (result.isSchemaError()) schemaErrorInSingleResultFound = true; @@ -91,19 +136,18 @@ public void doesNotHaveSchemaErrorTest() { @Test public void multipleDocumentsWithAndWithoutSchemaErrorTest() { for (int curCCDAFileIndex = 0; curCCDAFileIndex < LAST_SCHEMA_TEST_AND_NO_SCHEMA_ERROR_INDEX + 1; curCCDAFileIndex++) { - System.out - .println("***************** Running multipleDocumentsWithAndWithoutSchemaErrorTest test #" + println("***************** Running multipleDocumentsWithAndWithoutSchemaErrorTest test #" + (curCCDAFileIndex + 1) + " ******************" + System.lineSeparator()); - ArrayList results = validateDocumentAndReturnResults(convertCCDAFileToString(CCDA_FILES[curCCDAFileIndex])); + ArrayList results = + validateDocumentAndReturnResults(convertCCDAFileToString(CCDA_FILES[curCCDAFileIndex])); - System.out - .println(System.lineSeparator() - + "CCDAIssueStates.hasSchemaError(): " - + mdhtResultsHaveSchemaError(results) - + System.lineSeparator()); + println(System.lineSeparator() + + "CCDAIssueStates.hasSchemaError(): " + + mdhtResultsHaveSchemaError(results) + + System.lineSeparator()); if (curCCDAFileIndex == 0 || curCCDAFileIndex == LAST_SCHEMA_TEST_AND_NO_SCHEMA_ERROR_INDEX) { assertFalse( @@ -125,60 +169,388 @@ public void multipleDocumentsWithAndWithoutSchemaErrorTest() { } } - System.out.println("***************** End results for test #" + println("***************** End results for test #" + (curCCDAFileIndex + 1) + " ******************" + System.lineSeparator() + System.lineSeparator()); } } + + @Test + public void igOrMu2SchemaErrorsFileTest() { + runIgOrMu2AndNotSchemaTests(HAS_SCHEMA_ERROR_INDEX, CCDATypes.CLINICAL_OFFICE_VISIT_SUMMARY, true); + } + + @Test + public void igOrMu2NoSchemaErrorsHasMU2ErrorsFileTest() { + runIgOrMu2AndNotSchemaTests(HAS_4_POSSIBLE_CONSOL_AND_1_POSSIBLE_MU2_ERROR, + CCDATypes.TRANSITIONS_OF_CARE_AMBULATORY_SUMMARY, false); + } + + private static void runIgOrMu2AndNotSchemaTests(final int ccdaFileIndex, String ccdaTypesObjective, + boolean shouldHaveSchemaErrors) { + ArrayList results = + validateDocumentAndReturnResults(convertCCDAFileToString(CCDA_FILES[ccdaFileIndex]), + ccdaTypesObjective); + + if(SHOW_ERRORS_ONLY) { + printResults(getMDHTErrorsFromResults(results)); + } else { + printResults(results); + } + + boolean hasSchemaError = false; + for (RefCCDAValidationResult result : getMDHTErrorsFromResults(results)) { + if (result.isSchemaError()) { + hasSchemaError = true; + } + } + if(shouldHaveSchemaErrors) { + assertTrue("The document IS supposed to have a schema error but one was NOT flagged", hasSchemaError); + } else { + assertFalse("The document is NOT supposed to have a schema error but one WAS flagged", hasSchemaError); + } + final String msgSuffix = " Issue cannot also be a schema error of type: "; + final String msgSuffixSchemaError = msgSuffix + "isSchemaError"; + final String msgSuffixDatatypeSchemaError = msgSuffix + "isDatatypeError"; + for (RefCCDAValidationResult result : results) { + if(result.getDescription().startsWith("Consol")) { + assertTrue("The issue (" + result.getDescription() + ") is an IG Issue but was not flagged as such", result.isIGIssue()); + assertFalse("The issue (" + result.getDescription() + ") is an IG Issue but was flagged as an MU Issue", result.isMUIssue()); + assertFalse("An IG" + msgSuffixSchemaError + " (" + result.getDescription() + ")", result.isSchemaError()); + assertFalse("An IG" + msgSuffixDatatypeSchemaError + " (" + result.getDescription() + ")", result.isDataTypeSchemaError()); + } else if(result.getDescription().startsWith("Mu2consol")) { + assertTrue("The issue (" + result.getDescription() + ") is an MU Issue but was not flagged as such", result.isMUIssue()); + assertFalse("The issue (" + result.getDescription() + ") is an MU Issue but was flagged as an IG Issue", result.isIGIssue()); + assertFalse("An Mu2" + msgSuffixSchemaError + " (" + result.getDescription() + ")", result.isSchemaError()); + assertFalse("An Mu2" + msgSuffixDatatypeSchemaError + " (" + result.getDescription() + ")", result.isDataTypeSchemaError()); + } + } + } + + @Test + public void invalidSnippetOnlyValidationResultsTest() { + ArrayList results = + validateDocumentAndReturnResults(convertCCDAFileToString(CCDA_FILES[INVALID_SNIPPET_ONLY_INDEX])); + assertTrue( + "The results should be null because a SAXParseException should have been thrown", + results == null); + println("Note: As indicated by a pass, the SAXParseException is expected for the document tested."); + } + + @Test + public void invalidSnippetOnlyServiceErrorTest() { + ValidationResultsDto results = runReferenceCCDAValidationServiceAndReturnResults( + CCDATypes.NON_SPECIFIC_CCDAR2, INVALID_SNIPPET_ONLY_INDEX); + handleServiceErrorTest(results); + } + + @Test + public void classCastMDHTExceptionThrownServiceErrorTest() { + ValidationResultsDto results = runReferenceCCDAValidationServiceAndReturnResults( + CCDATypes.NON_SPECIFIC_CCDAR2, + NON_CCDA_XML_HTML_FILE_WITH_XML_EXTENSION_INDEX); + handleServiceErrorTest(results); + } + + @Test + public void altSaxParseMDHTExceptionThrownServiceErrorTest() { + ValidationResultsDto results = runReferenceCCDAValidationServiceAndReturnResults( + CCDATypes.NON_SPECIFIC_CCDAR2, BLANK_EMPTY_DOCUMENT_INDEX); + handleServiceErrorTest(results); + } + + private String handleServiceErrorTest(ValidationResultsDto results) { + return handleServiceErrorTest(results, true); + } + + private static String handleServiceErrorTest(ValidationResultsDto results, boolean expectException) { + boolean isServiceError = results.getResultsMetaData().isServiceError() + && (results.getResultsMetaData().getServiceErrorMessage() != null && !results + .getResultsMetaData().getServiceErrorMessage() + .isEmpty()); + if (isServiceError) { + println("Service Error Message: " + + results.getResultsMetaData().getServiceErrorMessage()); + } + if (expectException) { + assertTrue( + "The results are supposed to contain a service error since the snippet sent is invalid", + isServiceError); + } else { + assertFalse( + "The results are NOT supposed to contain a service error the XML file sent is valid", + isServiceError); + } + return results.getResultsMetaData().getServiceErrorMessage(); + } + + @Test + public void consolOnlyResultsRemainAfterSwitchAndBackTest() { + handlePackageSwitchAndBackTestChoice( + CCDATypes.NON_SPECIFIC_CCDAR2, + convertCCDAFileToString(CCDA_FILES[HAS_4_POSSIBLE_CONSOL_AND_1_POSSIBLE_MU2_ERROR])); + } + + @Test + public void mu2ResultsAreRemovedAfterSwitchAndBackTest() { + handlePackageSwitchAndBackTestChoice( + CCDATypes.TRANSITIONS_OF_CARE_AMBULATORY_SUMMARY, + convertCCDAFileToString(CCDA_FILES[HAS_4_POSSIBLE_CONSOL_AND_1_POSSIBLE_MU2_ERROR])); + } + + private static void handlePackageSwitchAndBackTestChoice(String firstTestCCDATypesType, String ccdaFileAsString) { + List results = validateDocumentAndReturnResults(ccdaFileAsString, firstTestCCDATypesType); + assertTrue("MDHT Errors must be returned", hasMDHTValidationErrors(results)); + + if (firstTestCCDATypesType.equals(CCDATypes.NON_SPECIFIC_CCDAR2) + || firstTestCCDATypesType.equals(CCDATypes.NON_SPECIFIC_CCDA)) { + println("check original results to ensure there are NO MU2 results (or that there ARE MU2 Results if MU2 type"); + List mdhtErrors = getMDHTErrorsFromResults(results); + printResults(mdhtErrors, false, false, false); + assertFalse("Since this was not an MU2 validation, Mu2consolPackage results should NOT have been returned", + mdhtErrorsHaveProvidedPackageResult(mdhtErrors, CCDATypes.CCDAR11_MU2)); + println("run a new validation against MU2 and ensure there ARE MU2 results"); + mdhtErrors = getMDHTErrorsFromResults(validateDocumentAndReturnResults( + ccdaFileAsString, CCDATypes.TRANSITIONS_OF_CARE_AMBULATORY_SUMMARY)); + printResults(mdhtErrors, false, false, false); + assertTrue("Since this WAS an MU2 validation, Mu2consolPackage results SHOULD have been returned", + mdhtErrorsHaveProvidedPackageResult(mdhtErrors, CCDATypes.CCDAR11_MU2)); + println("run a final validation against Consol and ensure there are NO MU2 results and that there ARE Consol Results"); + mdhtErrors = getMDHTErrorsFromResults(validateDocumentAndReturnResults( + ccdaFileAsString, CCDATypes.NON_SPECIFIC_CCDA)); + printResults(mdhtErrors, false, false, false); + assertFalse("Since this was a Consol validation (reverted from MU2), Mu2consolPackage results should NOT have been returned", + mdhtErrorsHaveProvidedPackageResult(mdhtErrors, CCDATypes.CCDAR11_MU2)); + assertTrue("ConsolPackage results SHOULD have been returned since the document was tested against Consol and contains errors", + mdhtErrorsHaveProvidedPackageResult(mdhtErrors, CCDATypes.CCDAR21_OR_CCDAR11)); + } else if(firstTestCCDATypesType.equals(CCDATypes.TRANSITIONS_OF_CARE_AMBULATORY_SUMMARY)) { + println("check original results to ensure there ARE MU2 results"); + List mdhtErrors = getMDHTErrorsFromResults(results); + printResults(mdhtErrors, false, false, false); + assertTrue("Since this is an MU2 validation, Mu2consolPackage results SHOULD have been returned", + mdhtErrorsHaveProvidedPackageResult(mdhtErrors, CCDATypes.CCDAR11_MU2)); + println("run a new validation against Consol and ensure there are NO MU2 results)"); + mdhtErrors = getMDHTErrorsFromResults(validateDocumentAndReturnResults( + ccdaFileAsString, CCDATypes.NON_SPECIFIC_CCDAR2)); + printResults(mdhtErrors, false, false, false); + assertFalse("Since this was NOT an MU2 validation, Mu2consolPackage results should NOT have been returned", + mdhtErrorsHaveProvidedPackageResult(mdhtErrors, CCDATypes.CCDAR11_MU2)); + println("run a final validation against MU2 and ensure the MU2 results HAVE RETURNED"); + mdhtErrors = getMDHTErrorsFromResults(validateDocumentAndReturnResults( + ccdaFileAsString, CCDATypes.TRANSITIONS_OF_CARE_INPATIENT_SUMMARY)); + printResults(mdhtErrors, false, false, false); + assertTrue("Since this was an MU2 validation (reverted from Consol), Mu2consolPackage results SHOULD have been returned", + mdhtErrorsHaveProvidedPackageResult(mdhtErrors, CCDATypes.CCDAR11_MU2)); + assertTrue("ConsolPackage results SHOULD have been returned as well since MU2 inherits from consol " + + "and the doc tested has base level errors", + mdhtErrorsHaveProvidedPackageResult(mdhtErrors, CCDATypes.CCDAR21_OR_CCDAR11)); + } + } + + @Test + public void invalidValidationObjectiveSentTest() { + ValidationResultsDto results = runReferenceCCDAValidationServiceAndReturnResults( + "INVALID VALIDATION OBJECTIVE", HAS_4_POSSIBLE_CONSOL_AND_1_POSSIBLE_MU2_ERROR); + final String msg = handleServiceErrorTest(results); + final String match = "invalid"; + assertTrue("The service error returned did not contain: " + match, msg.contains(match)); + } + + @Test + public void emptyStringValidationObjectiveSentTest() { + ValidationResultsDto results = runReferenceCCDAValidationServiceAndReturnResults( + "", HAS_4_POSSIBLE_CONSOL_AND_1_POSSIBLE_MU2_ERROR); + final String msg = handleServiceErrorTest(results); + final String match = "empty"; + assertTrue("The service error returned did not contain: " + match, msg.contains(match)); + } + + @Test + public void allPossibleValidValidationObjectivesSentTest() { + for (String objective : ValidationObjectives.ALL_UNIQUE) { + List results = getMDHTErrorsFromResults(validateDocumentAndReturnResults( + convertCCDAFileToString(CCDA_FILES[HAS_4_POSSIBLE_CONSOL_AND_1_POSSIBLE_MU2_ERROR]), objective)); + printResults(results, false, false, false); + assertTrue(results != null && !results.isEmpty()); + } + } + + @Test + public void allPossibleValidCcdaTypesSentTest() { + List legacyAndMu2Types = new ArrayList(); + legacyAndMu2Types.addAll(CCDATypes.NON_SPECIFIC_CCDA_TYPES); + legacyAndMu2Types.addAll(CCDATypes.MU2_TYPES); + for (String type : legacyAndMu2Types) { + List results = getMDHTErrorsFromResults(validateDocumentAndReturnResults( + convertCCDAFileToString(CCDA_FILES[HAS_4_POSSIBLE_CONSOL_AND_1_POSSIBLE_MU2_ERROR]), type)); + printResults(results, false, false, false); + assertTrue(results != null && !results.isEmpty()); + } + } + + @Ignore + @Test + public void basicNoExceptionServiceTest() { + ValidationResultsDto results = runReferenceCCDAValidationServiceAndReturnResults( + ValidationObjectives.Sender.B1_TOC_AMB_170_315, HAS_4_POSSIBLE_CONSOL_AND_1_POSSIBLE_MU2_ERROR); + handleServiceErrorTest(results, false); + } + + private static boolean hasMDHTValidationErrors(List results) { + return !getMDHTErrorsFromResults(results).isEmpty(); + } + + private static List getMDHTErrorsFromResults(List results) { + List mdhtErrors = new ArrayList(); + for (RefCCDAValidationResult result : results) { + if (result.getType() == ValidationResultType.CCDA_MDHT_CONFORMANCE_ERROR) { + mdhtErrors.add(result); + } + } + return mdhtErrors; + } + + private static boolean mdhtErrorsHaveProvidedPackageResult(List mdhtErrors, String ccdaTypeToCheckFor) { + boolean hasMu2 = false, hasConsol = false; + for(RefCCDAValidationResult mdhtError : mdhtErrors) { + if(mdhtError.getDescription().contains("Mu2consol")) { + hasMu2 = true; + } + if(mdhtError.getDescription().contains("Consol")) { + hasConsol = true; + } + } + if(ccdaTypeToCheckFor.equals(CCDATypes.CCDAR11_MU2)) { + return hasMu2; + } else if(ccdaTypeToCheckFor.equals(CCDATypes.CCDAR21_OR_CCDAR11)) { + return hasConsol; + } + return false; + } private static String convertCCDAFileToString(URI ccdaFileURL) { StringBuilder sb = new StringBuilder(); BufferedReader br = null; try { br = new BufferedReader(new FileReader(ccdaFileURL.getPath())); - String sCurrentLine = ""; while ((sCurrentLine = br.readLine()) != null) { sb.append(sCurrentLine); } } catch (Exception e) { - System.out.println(e.toString()); + println(e.toString()); } finally { try { br.close(); } catch (IOException e) { - e.printStackTrace(); + if(LOG_RESULTS_TO_CONSOLE) e.printStackTrace(); } } return sb.toString(); } + + private static ValidationResultsDto runReferenceCCDAValidationServiceAndReturnResults( + String validationObjective, final int XML_FILE_INDEX) { +// MultipartFile mockSample = new MockMultipartFile("ccdaFileActualName", "ccdaFileOriginalName", +// "text/xml", convertCCDAFileToString(CCDA_FILES[XML_FILE_INDEX]).getBytes()); + File file = new File(CCDA_FILES[XML_FILE_INDEX]); + ReferenceCCDAValidationService referenceCcdaValidationService = null; + MultipartFile mockSample = null; + try(FileInputStream is = new FileInputStream(file)) { + mockSample = new MockMultipartFile("ccdaFileActualName", "ccdaFileOriginalName", + "text/xml", is); + referenceCcdaValidationService = new ReferenceCCDAValidationService( + new ReferenceCCDAValidator(), new VocabularyCCDAValidator( + new VocabularyValidationService()), + new ReferenceContentValidator(new ContentValidatorService())); + } catch (IOException e) { + e.printStackTrace(); + } + if(referenceCcdaValidationService == null || mockSample == null) { + Assert.fail("referenceCcdaValidationService == null || mockSample == null"); + throw new NullPointerException("referenceCcdaValidationService is " + + (referenceCcdaValidationService == null ? "null" : "not null") + + ("mockSample is " + mockSample == null ? "null" : "not null")); + } + return referenceCcdaValidationService.validateCCDA(validationObjective, "", mockSample); + } - private static ArrayList validateDocumentAndReturnResults( - String ccdaFileAsString) { + private static ArrayList validateDocumentAndReturnResults(String ccdaFileAsString) { + return validateDocumentAndReturnResults(ccdaFileAsString, CCDATypes.NON_SPECIFIC_CCDAR2); + } + + private static ArrayList validateDocumentAndReturnResults(String ccdaFileAsString, + String validationObjective) { ReferenceCCDAValidator referenceCCDAValidator = new ReferenceCCDAValidator(); ArrayList results = null; try { - results = referenceCCDAValidator.validateFile("Test", "Test", + results = referenceCCDAValidator.validateFile(validationObjective, "Test", ccdaFileAsString); } catch (SAXException e) { - e.printStackTrace(); + if(LOG_RESULTS_TO_CONSOLE) e.printStackTrace(); + } catch (Exception e) { + if(LOG_RESULTS_TO_CONSOLE) e.printStackTrace(); } return results; } + private static void printResults(List results) { + printResults(results, true, true, true); + } + + private static void printResults(List results, + boolean showSchema, boolean showType, boolean showIgOrMuType) { + if (LOG_RESULTS_TO_CONSOLE) { + if (!results.isEmpty()) { + for (RefCCDAValidationResult result : results) { + printResults(result, showSchema, showType, showIgOrMuType); + } + println(); + } else { + println("There are no results to print as the list is empty."); + } + } + } + private static void printResults(RefCCDAValidationResult result) { - System.out.println("Description : " + result.getDescription()); - System.out.println("Type : " + result.getType()); - System.out - .println("result.isSchemaError() : " + result.isSchemaError()); - System.out.println("result.isDataTypeSchemaError() : " - + result.isDataTypeSchemaError()); - System.out.println(); - } - - private boolean mdhtResultsHaveSchemaError(List mdhtResults) { - for(RefCCDAValidationResult result : mdhtResults){ - if(result.isSchemaError()){ + printResults(result, true, true, true); + } + + private static void printResults(RefCCDAValidationResult result, + boolean showSchema, boolean showType, boolean showIgOrMuType) { + if (LOG_RESULTS_TO_CONSOLE) { + println("Description : " + result.getDescription()); + if(showType) { + println("Type : " + result.getType()); + } + if(showSchema) { + println("result.isSchemaError() : " + result.isSchemaError()); + println("result.isDataTypeSchemaError() : " + result.isDataTypeSchemaError()); + } + if(showIgOrMuType) { + println("result.isIGIssue() : " + result.isIGIssue()); + println("result.isMUIssue() : " + result.isMUIssue()); + } + } + } + + private static void println() { + if (LOG_RESULTS_TO_CONSOLE) System.out.println(); + } + + private static void println(String message) { + print(message); + println(); + } + + private static void print(String message) { + if (LOG_RESULTS_TO_CONSOLE) System.out.print(message); + } + + private boolean mdhtResultsHaveSchemaError( + List mdhtResults) { + for (RefCCDAValidationResult result : mdhtResults) { + if (result.isSchemaError()) { return true; } } diff --git a/src/test/resources/Sample_CCDA_CCD_b1_Ambulatory_v2.xml b/src/test/resources/Sample_CCDA_CCD_b1_Ambulatory_v2.xml new file mode 100644 index 00000000..1b6402a0 --- /dev/null +++ b/src/test/resources/Sample_CCDA_CCD_b1_Ambulatory_v2.xml @@ -0,0 +1,3761 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Community Health and Hospitals: Health Summary + + + + + + + + + + + + + + + + + + + + + + + + + + + 1357 Amber Drive + Beaverton + OR + 97006 + US + + + + + + + + + + + + + + Myra + + Jones + + + + + + + + + + + + + + + + 1357 Amber Drive + Beaverton + OR + 97006 + US + + + + + + Ralph + Jones + + + + + + + Beaverton + OR + 97006 + US + + + + + + + + + + + + + + Community Health and Hospitals + + + 1002 Healthcare Dr + Portland + OR + 97266 + US + + + + + + + + + + + + 1002 Healthcare Drive + Portland + OR + 97266 + US + + + + + Mary + McDonald + + + + + + + + + 1002 Healthcare Drive + Portland + OR + 97266 + US + + + + + Henry + Seven + + + + + + + + + + + Frank + Jones + + + + + + + + + Community Health and Hospitals + + + 1002 Healthcare Drive + Portland + OR + 97266 + US + + + + + + + + + Henry + Seven + + + + Community Health and Hospitals + + + + + + + + + + + + + 1357 Amber Drive + Beaverton + OR + 97006 + US + + + + + + Mr. + Ralph + Jones + + + + + + + + + + 1357 Amber Drive + Beaverton + OR + 97006 + US + + + + + + Mr. + Frank + Jones + + + + + + + + + + + + + + + Primary Care Provider + + + + + + + 1002 Healthcare Dr + Portland + OR + 97266 + US + + + + + Dr. + Henry + Seven + + + + + Community Health and Hospitals + + + 1002 Healthcare Drive + Portland + OR + 97266 + US + + + + + + + Primary Care Provider + + + + + + + 1002 Healthcare Dr + Portland + OR + 97266 + US + + + + + Dr. + Henry + Seven + + + + + Community Health and Hospitals + + + 1002 Healthcare Drive + Portland + OR + 97266 + US + + + + + + + + + + + + + + + + + + + + + Dr + Henry + Seven + + + + + + + + + + Dr + Henry + Seven + + + + + + + + + + + + + + + + + + +
+ + + + ALLERGIES, ADVERSE REACTIONS, ALERTS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SubstanceReactionSeverityStatus
Penicillin G benzathineHivesModerate to severeInactive
CodeineShortness of BreathModerateActive
AspirinHivesMild to moderateActive
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + + ENCOUNTERS + + + + + + + + + + + + + + + + + + +
EncounterPerformerLocationDate
+ PnuemoniaDr Henry SevenCommunity Health and Hospitals20120806
+
+ + + + + + + + Mild Fever + + + + + + + + + + + + + + + + 1002 Healthcare Dr + Portland + OR + 97266 + US + + + + Community Health and Hospitals + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + + + IMMUNIZATIONS + + + + + + + + + + + + + + + + + + + + + +
VaccineDateStatus
Influenza virus vaccine, IMMay 2012Completed
Tetanus and diphtheria toxoids, IMAor 2012Completed
+
+ + + + + + + + + + + + + + + + + + + + Influenza virus vaccine + + + + + Health LS - Immuno Inc. + + + + + + + + + Possible flu-like symptoms for three days. + + + + + + + + + + + + + + + + + + + + + + + + Tetanus and diphtheria toxoids - preservative free + + + + + Health LS - Immuno Inc. + + + + + + + + + Possible flu-like symptoms for three days. + + + + + +
+
+ + + + +
+ + + Medications + + + + + + + + + + + + + + + + + + + + + + +
MedicationDirectionsStart DateStatusIndicationsFill Instructions
Albuterol 0.09 MG/ACTUAT inhalant solution0.09 MG/ACTUAT inhalant solution, 2 puffs once20120806ActivePneumonia (233604007 SNOMED CT)Generic Substitition Allowed
+
+ + + + + + + 0.09 MG/ACTUAT inhalant solution, 2 puffs + + + + + + + + + + + + + + + + + + + + + + + + Medication Factory Inc. + + + + + + + + + + + Community Health and Hospitals + + + + + + + + + + + + Aerosol + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Medication Factory Inc. + + + + + + + + 1002 Healthcare Dr + Portland + OR + 97266 + US + + + + + + + + + + + label in spanish + + + + + + + + + + + + + + + + + + + + + + + + + Medication Factory Inc. + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + + CARE PLAN + + + + + + + + + + + + + + + + + + + + + + +
Planned ActivityPlanned Date
Consultation with Dr George Potomac for Asthma20120820
Chest X-ray20120826
Sputum Culture20120820
+
+ + + + + + + +
+ + + + + + + Follow up with Dr George Potomac for Asthma + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ + + +
+
+ + + +
+ + + + + HOSPITAL DISCHARGE MEDICATIONS + + + + + + + + + + + + + + + + + + + + + + +
MedicationDirectionsStart DateStatusIndicationsFill Instructions
120 ACTUAT Fluticasone propionate 0.11 MG/ACTUAT Metered Dose Inhaler0.11 MG/ACTUAT Metered Dose Once Daily20120813ActiveBronchitis (32398004 SNOMED CT)Generic Substitition Allowed
+
+ + + + + + + + + + + + + + + + + + 0.11 MG/ACTUAT Metered Dose Once Daily + + + + + + + + + + + + + + + + + + + + + + + + + + + + Medication Factory Inc. + + + + + + + + + + + Community Health and Hospitals + + + + + + + + + + + + Aerosol + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Medication Factory Inc. + + + + + + + + 1002 Healthcare Dr + Portland + OR + 97266 + US + + + + + + + + + + + Generic Substitution Allowed + + + + + + + + + + + + + + + + + + + + + + + + + + Medication Factory Inc. + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + + REASON FOR REFERRAL + + Follow up with Dr George Potomac for Asthma + +
+
+ + +
+ + + PROBLEMS + + + Pneumonia : Status - Resolved + Asthma : Status - Active + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ + + + PROCEDURES + + + + + + + + + + + + + + +
ProcedureDate
+ Chest X-Ray + 8/7/2012
+
+ + + + + + + + + + + + + + + + + + + + 1002 Healthcare Dr + Portland + OR + 97266 + US + + + + + Community Health and Hospitals + + + + + + + + + + + + 1002 Healthcare Dr + Portland + OR + 97266 + US + + + + Community Health and Hospitals + + + + + +
+
+ + + +
+ + + + FUNCTIONAL STATUS + + + + + + + + + + + + + + + + + + + + + +
Functional ConditionEffective DatesCondition Status
+ Dependence on cane2008Active
+ Memory impairment + 2008Active
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + + RESULTS + + + + + + + + + + + + + + + + + + + + + + +
LABORATORY INFORMATION
Chemistries and drug levels
HGB (M 13-18 g/dl; F 12-16 g/dl)13.2
WBC (4.3-10.8 10+3/ul)6.7
PLT (135-145 meq/l)123 (L)
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + M 13-18 g/dl; F 12-16 g/dl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + + SOCIAL HISTORY + + + + + + + + + + + + + + + + + + + + + +
Social History ElementDescriptionEffective Dates
+ smokingFormer Smoker (1 pack per day20050501 to 20110227
+ smokingCurrent Everyday Smoker 2 packs per day20110227 - today
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 pack per day + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ + + VITAL SIGNS + + + + + + + + + + + + + + + + + + + + + + + + + + +
Date / Time: Nov 1, 2011August 6, 2012
Height + 69 inches + 69 inches
Weight + 189 lbs + 194 lbs
Blood Pressure132/86 mmHg145/88 mmHg
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + HOSPITAL DISCHARGE INSTRUCTIONS + + + Ms. Jones, you have been seen by Dr. Henry Seven at Local Community Hospital from August 8th until August 13th 2012. You are currently being discharged from Local Community Hospital. Dr. Seven has provided the following instructions to you at this time; should you have any questions please contact a member of your healthcare team prior to discharge. If you have left the hospital and have questions, please contact Dr. Seven at 555-555-1002. + Instructions: + + + Take all medications as prescribed. + Please monitor your peak flows. If your peak flows drop to 50% of normal, call my office immediately or return to the Emergency Room. + If you experience any of the following symptoms, call my office immediately or return to the Emergency Room: + + Shortness of Breath + Dizziness or Light-headedness + Fever, chills, or diffuse body aches + Pain or redness at the site of any previous intravenous catheter + Any other unusual problem + + + + +
+
+ +
+
+
diff --git a/src/test/resources/Sample_basicHTML.xml b/src/test/resources/Sample_basicHTML.xml new file mode 100644 index 00000000..a95c2e72 --- /dev/null +++ b/src/test/resources/Sample_basicHTML.xml @@ -0,0 +1,8 @@ + + + + +

Not a C-CDA

+

Nothin here...

+ + diff --git a/src/test/resources/Sample_blank_Empty_Document.xml b/src/test/resources/Sample_blank_Empty_Document.xml new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/Sample_invalid-SnippetOnly.xml b/src/test/resources/Sample_invalid-SnippetOnly.xml new file mode 100644 index 00000000..d8558145 --- /dev/null +++ b/src/test/resources/Sample_invalid-SnippetOnly.xml @@ -0,0 +1,82 @@ + +
+ + + + + Results CO2 Example + + + + + + + + + + + + + + + + + + + + + + +
ResultValueUnitsRangeDateInterpretation
CO227mmol/L23-29 mmol/L8/15/2012Normal
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 23-29 mmol/L + + + + + + + + + + + + +
+
+