/*******************************************************************************
* Open Behavioral Health Information Technology Architecture (OBHITA.org)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the <organization> nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package gov.samhsa.acs.documentsegmentation;
import static gov.samhsa.acs.audit.AcsAuditVerb.SEGMENT_DOCUMENT;
import static gov.samhsa.acs.audit.AcsPredicateKey.CATEGORY_OBLIGATIONS_APPLIED;
import static gov.samhsa.acs.audit.AcsPredicateKey.ORIGINAL_DOCUMENT;
import static gov.samhsa.acs.audit.AcsPredicateKey.ORIGINAL_DOCUMENT_VALID;
import static gov.samhsa.acs.audit.AcsPredicateKey.RULES_FIRED;
import static gov.samhsa.acs.audit.AcsPredicateKey.SECTION_OBLIGATIONS_APPLIED;
import static gov.samhsa.acs.audit.AcsPredicateKey.SEGMENTED_DOCUMENT;
import static gov.samhsa.acs.audit.AcsPredicateKey.SEGMENTED_DOCUMENT_VALID;
import gov.samhsa.acs.audit.AuditService;
import gov.samhsa.acs.audit.PredicateKey;
import gov.samhsa.acs.brms.RuleExecutionService;
import gov.samhsa.acs.brms.domain.ClinicalFact;
import gov.samhsa.acs.brms.domain.FactModel;
import gov.samhsa.acs.brms.domain.RuleExecutionContainer;
import gov.samhsa.acs.brms.domain.XacmlResult;
import gov.samhsa.acs.common.exception.DS4PException;
import gov.samhsa.acs.common.tool.SimpleMarshaller;
import gov.samhsa.acs.common.validation.XmlValidation;
import gov.samhsa.acs.common.validation.XmlValidationResult;
import gov.samhsa.acs.common.validation.exception.XmlDocumentReadFailureException;
import gov.samhsa.acs.documentsegmentation.dto.SegmentDocumentResponse;
import gov.samhsa.acs.documentsegmentation.exception.InvalidSegmentedClinicalDocumentException;
import gov.samhsa.acs.documentsegmentation.tools.AdditionalMetadataGeneratorForSegmentedClinicalDocument;
import gov.samhsa.acs.documentsegmentation.tools.DocumentEditor;
import gov.samhsa.acs.documentsegmentation.tools.DocumentFactModelExtractor;
import gov.samhsa.acs.documentsegmentation.tools.DocumentRedactor;
import gov.samhsa.acs.documentsegmentation.tools.DocumentTagger;
import gov.samhsa.acs.documentsegmentation.tools.EmbeddedClinicalDocumentExtractor;
import gov.samhsa.acs.documentsegmentation.tools.dto.RedactedDocument;
import gov.samhsa.acs.documentsegmentation.valueset.ValueSetService;
import gov.samhsa.acs.documentsegmentation.valueset.dto.CodeAndCodeSystemSetDto;
import gov.samhsa.consent2share.schema.ruleexecutionservice.AssertAndExecuteClinicalFactsResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import javax.activation.DataHandler;
import javax.xml.bind.JAXBException;
import org.apache.axiom.attachments.ByteArrayDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.xml.sax.SAXParseException;
import ch.qos.logback.audit.AuditException;
/**
* The Class DocumentSegmentationImpl.
*/
public class DocumentSegmentationImpl implements DocumentSegmentation {
/** The logger. */
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/** The rule execution web service client. */
private final RuleExecutionService ruleExecutionService;
/** The audit service. */
private final AuditService auditService;
/** The document editor. */
private final DocumentEditor documentEditor;
/** The marshaller. */
private final SimpleMarshaller marshaller;
/** The document redactor. */
private final DocumentRedactor documentRedactor;
/** The document tagger. */
private final DocumentTagger documentTagger;
/** The document fact model extractor. */
private final DocumentFactModelExtractor documentFactModelExtractor;
/** The embedded clinical document extractor. */
private final EmbeddedClinicalDocumentExtractor embeddedClinicalDocumentExtractor;
/** The value set service. */
private final ValueSetService valueSetService;
/** The additional metadata generator for segmented clinical document. */
private final AdditionalMetadataGeneratorForSegmentedClinicalDocument additionalMetadataGeneratorForSegmentedClinicalDocument;
/** The xml validator. */
private final XmlValidation xmlValidator;
/** The Constant C32_CDA_XSD_PATH. */
public static final String C32_CDA_XSD_PATH = "schema/cdar2c32/infrastructure/cda/";
/** The Constant C32_CDA_XSD_NAME. */
public static final String C32_CDA_XSD_NAME = "C32_CDA.xsd";
/**
* Instantiates a new document processor impl.
*
* @param ruleExecutionService
* the rule execution service
* @param auditService
* the audit service
* @param documentEditor
* the document editor
* @param marshaller
* the marshaller
* @param documentRedactor
* the document redactor
* @param documentTagger
* the document tagger
* @param documentFactModelExtractor
* the document fact model extractor
* @param embeddedClinicalDocumentExtractor
* the embedded clinical document extractor
* @param valueSetService
* the value set service
* @param additionalMetadataGeneratorForSegmentedClinicalDocument
* the additional metadata generator for segmented clinical
* document
*/
public DocumentSegmentationImpl(
RuleExecutionService ruleExecutionService,
AuditService auditService,
DocumentEditor documentEditor,
SimpleMarshaller marshaller,
DocumentRedactor documentRedactor,
DocumentTagger documentTagger,
DocumentFactModelExtractor documentFactModelExtractor,
EmbeddedClinicalDocumentExtractor embeddedClinicalDocumentExtractor,
ValueSetService valueSetService,
AdditionalMetadataGeneratorForSegmentedClinicalDocument additionalMetadataGeneratorForSegmentedClinicalDocument) {
this.ruleExecutionService = ruleExecutionService;
this.auditService = auditService;
this.documentEditor = documentEditor;
this.marshaller = marshaller;
this.documentRedactor = documentRedactor;
this.documentTagger = documentTagger;
this.documentFactModelExtractor = documentFactModelExtractor;
this.embeddedClinicalDocumentExtractor = embeddedClinicalDocumentExtractor;
this.valueSetService = valueSetService;
this.additionalMetadataGeneratorForSegmentedClinicalDocument = additionalMetadataGeneratorForSegmentedClinicalDocument;
this.xmlValidator = createXmlValidator();
}
/**
* Instantiates a new document segmentation impl.
*
* @param ruleExecutionService
* the rule execution service
* @param documentEditor
* the document editor
* @param marshaller
* the marshaller
* @param documentRedactor
* the document redactor
* @param documentTagger
* the document tagger
* @param documentFactModelExtractor
* the document fact model extractor
* @param embeddedClinicalDocumentExtractor
* the embedded clinical document extractor
* @param valueSetService
* the value set service
* @param additionalMetadataGeneratorForSegmentedClinicalDocument
* the additional metadata generator for segmented clinical
* document
*/
public DocumentSegmentationImpl(
RuleExecutionService ruleExecutionService,
DocumentEditor documentEditor,
SimpleMarshaller marshaller,
DocumentRedactor documentRedactor,
DocumentTagger documentTagger,
DocumentFactModelExtractor documentFactModelExtractor,
EmbeddedClinicalDocumentExtractor embeddedClinicalDocumentExtractor,
ValueSetService valueSetService,
AdditionalMetadataGeneratorForSegmentedClinicalDocument additionalMetadataGeneratorForSegmentedClinicalDocument) {
this.ruleExecutionService = ruleExecutionService;
this.auditService = null;
this.documentEditor = documentEditor;
this.marshaller = marshaller;
this.documentRedactor = documentRedactor;
this.documentTagger = documentTagger;
this.documentFactModelExtractor = documentFactModelExtractor;
this.embeddedClinicalDocumentExtractor = embeddedClinicalDocumentExtractor;
this.valueSetService = valueSetService;
this.additionalMetadataGeneratorForSegmentedClinicalDocument = additionalMetadataGeneratorForSegmentedClinicalDocument;
this.xmlValidator = createXmlValidator();
}
@SuppressWarnings("unchecked")
@Override
public SegmentDocumentResponse segmentDocument(String document,
String enforcementPolicies, boolean isAudited,
boolean isAuditFailureByPass, boolean enableTryPolicyResponse)
throws XmlDocumentReadFailureException,
InvalidSegmentedClinicalDocumentException, AuditException {
Assert.notNull(document);
final String originalDocument = document;
XmlValidationResult originalClinicalDocumentValidationResult = null;
try {
originalClinicalDocumentValidationResult = xmlValidator
.validateWithAllErrors(document);
Assert.notNull(originalClinicalDocumentValidationResult);
} catch (final XmlDocumentReadFailureException e) {
logger.error(e.getMessage(), e);
throw e;
}
if (!originalClinicalDocumentValidationResult.isValid()) {
logger.error("Schema validation is failed for original clinical document.");
final String err = "InvalidOriginalClinicalDocumentException: ";
for (final SAXParseException e : originalClinicalDocumentValidationResult
.getExceptions()) {
logger.error(err + e.getMessage());
}
}
Assert.notNull(enforcementPolicies);
RuleExecutionContainer ruleExecutionContainer = null;
RedactedDocument redactedDocument = null;
String rulesFired = null;
final SegmentDocumentResponse segmentDocumentResponse = new SegmentDocumentResponse();
FactModel factModel = null;
try {
document = documentEditor.setDocumentCreationDate(document);
// FileHelper.writeStringToFile(document, "Original_C32.xml");
// extract factModel
String factModelXml = documentFactModelExtractor.extractFactModel(
document, enforcementPolicies);
// get clinical document with generatedEntryId elements
document = embeddedClinicalDocumentExtractor
.extractClinicalDocumentFromFactModel(factModelXml);
// remove the embedded c32 from factmodel before unmarshalling
factModelXml = documentRedactor
.cleanUpEmbeddedClinicalDocumentFromFactModel(factModelXml);
factModel = marshaller.unmarshalFromXml(FactModel.class,
factModelXml);
final List<CodeAndCodeSystemSetDto> codeAndCodeSystemSetDtoList = new ArrayList<CodeAndCodeSystemSetDto>();
// Get and set value set categories to clinical facts
for (final ClinicalFact fact : factModel.getClinicalFactList()) {
final CodeAndCodeSystemSetDto codeAndCodeSystemSetDto = new CodeAndCodeSystemSetDto();
codeAndCodeSystemSetDto.setConceptCode(fact.getCode());
codeAndCodeSystemSetDto.setCodeSystemOid(fact.getCodeSystem());
codeAndCodeSystemSetDtoList.add(codeAndCodeSystemSetDto);
}
// Get value set categories
final List<Map<String, Object>> valueSetCategories = valueSetService
.lookupValuesetCategoriesOfMultipleCodeAndCodeSystemSet(codeAndCodeSystemSetDtoList);
// Iterator<HashMap<String, String>> iterator=valueSetCategories.k
for (int i = 0; i < factModel.getClinicalFactList().size(); i++) {
final Map<String, Object> valueSetMap = valueSetCategories
.get(i);
final ClinicalFact fact = factModel.getClinicalFactList()
.get(i);
Assert.isTrue(fact.getCode().equals(
valueSetMap.get("conceptCode")));
Assert.isTrue(fact.getCodeSystem().equals(
valueSetMap.get("codeSystemOid")));
if (valueSetMap.get("vsCategoryCodes") != null) {
fact.setValueSetCategories(new HashSet<String>(
(ArrayList<String>) valueSetMap
.get("vsCategoryCodes")));
}
}
// FileHelper.writeStringToFile(factModel, "FactModel.xml");
// get execution response container
final AssertAndExecuteClinicalFactsResponse brmsResponse = ruleExecutionService
.assertAndExecuteClinicalFacts(factModel);
String executionResponseContainer = brmsResponse
.getRuleExecutionResponseContainer();
rulesFired = brmsResponse.getRulesFired();
// unmarshall from xml to RuleExecutionContainer
ruleExecutionContainer = marshaller.unmarshalFromXml(
RuleExecutionContainer.class, executionResponseContainer);
// FileHelper.writeStringToFile(executionResponseContainer,
// "ExecutionResponseContainer.xml");
logger.info("Fact model: " + factModelXml);
logger.info("Rule Execution Container size: "
+ ruleExecutionContainer.getExecutionResponseList().size());
// redact document
redactedDocument = documentRedactor.redactDocument(document,
ruleExecutionContainer, factModel);
document = redactedDocument.getRedactedDocument();
// set tryPolicyDocument in the response
if (enableTryPolicyResponse) {
segmentDocumentResponse
.setTryPolicyDocumentXml(redactedDocument
.getTryPolicyDocument());
}
// to get the itemActions from documentRedactor
executionResponseContainer = marshaller
.marshal(ruleExecutionContainer);
// tag document
document = documentTagger.tagDocument(document,
executionResponseContainer);
// clean up generatedEntryId elements from document
document = documentRedactor.cleanUpGeneratedEntryIds(document);
// clean up generatedServiceEventId elements from document
document = documentRedactor
.cleanUpGeneratedServiceEventIds(document);
// FileHelper.writeStringToFile(document, "Tagged_C32.xml");
// Set segmented document in response
segmentDocumentResponse.setSegmentedDocumentXml(document);
// Set execution response container in response
segmentDocumentResponse
.setExecutionResponseContainerXml(executionResponseContainer);
} catch (final JAXBException e) {
logger.error(e.getMessage(), e);
throw new DS4PException(e.toString(), e);
} catch (final Throwable e) {
logger.error(e.getMessage(), e);
throw new DS4PException(e.toString(), e);
}
XmlValidationResult segmentedClinicalDocumentValidationResult = null;
try {
segmentedClinicalDocumentValidationResult = xmlValidator
.validateWithAllErrors(document);
Assert.notNull(segmentedClinicalDocumentValidationResult);
if (isAudited) {
auditSegmentation(originalDocument, document,
factModel.getXacmlResult(), redactedDocument,
rulesFired, originalClinicalDocumentValidationResult,
segmentedClinicalDocumentValidationResult,
isAuditFailureByPass);
}
} catch (final XmlDocumentReadFailureException e) {
logger.error(e.getMessage(), e);
throw e;
}
if (!segmentedClinicalDocumentValidationResult.isValid()) {
final String segmentationErr = "Schema validation is failed for segmented clinical document.";
logger.error(segmentationErr);
final String err = "InvalidSegmentedClinicalDocumentException: ";
for (final SAXParseException e : segmentedClinicalDocumentValidationResult
.getExceptions()) {
logger.error(err + e.getMessage());
}
throw new InvalidSegmentedClinicalDocumentException(segmentationErr);
}
return segmentDocumentResponse;
}
/*
* (non-Javadoc)
*
* @see gov.samhsa.acs.documentsegmentation.DocumentSegmentation#
* setAdditionalMetadataForSegmentedClinicalDocument
* (gov.samhsa.consent2share
* .schema.documentsegmentation.SegmentDocumentResponse, java.lang.String,
* java.lang.String, java.lang.String,
* gov.samhsa.acs.brms.domain.XacmlResult)
*/
@Override
public void setAdditionalMetadataForSegmentedClinicalDocument(
SegmentDocumentResponse segmentDocumentResponse,
String senderEmailAddress, String recipientEmailAddress,
String xdsDocumentEntryUniqueId, XacmlResult xacmlResult) {
final String additionalMetadataForSegmentedClinicalDocument = additionalMetadataGeneratorForSegmentedClinicalDocument
.generateMetadataXml(xacmlResult.getMessageId(),
segmentDocumentResponse.getSegmentedDocumentXml(),
segmentDocumentResponse
.getExecutionResponseContainerXml(),
senderEmailAddress, recipientEmailAddress, xacmlResult
.getSubjectPurposeOfUse().getPurpose(),
xdsDocumentEntryUniqueId);
// FileHelper.writeStringToFile(additionalMetadataForSegmentedClinicalDocument,"additional_metadata.xml");
segmentDocumentResponse
.setPostSegmentationMetadataXml(additionalMetadataForSegmentedClinicalDocument);
}
/*
* (non-Javadoc)
*
* @see gov.samhsa.acs.documentsegmentation.DocumentSegmentation#
* setDocumentPayloadRawData
* (gov.samhsa.consent2share.schema.documentsegmentation
* .SegmentDocumentResponse, boolean, java.lang.String, java.lang.String,
* gov.samhsa.acs.brms.domain.XacmlResult)
*/
@Override
public void setDocumentPayloadRawData(
SegmentDocumentResponse segmentDocumentResponse,
boolean packageAsXdm, String senderEmailAddress,
String recipientEmailAddress, XacmlResult xacmlResult)
throws Exception, IOException {
final ByteArrayDataSource rawData = documentEditor
.setDocumentPayloadRawData(segmentDocumentResponse
.getSegmentedDocumentXml(), packageAsXdm,
senderEmailAddress, recipientEmailAddress, xacmlResult,
segmentDocumentResponse
.getExecutionResponseContainerXml(), null, null);
segmentDocumentResponse.setDocumentPayloadRawData(new DataHandler(
rawData));
}
/**
* Audit segmentation.
*
* @param originalDocument
* the original document
* @param segmentedDocument
* the segmented document
* @param xacmlResult
* the xacml result
* @param redactedDocument
* the redacted document
* @param rulesFired
* the rules fired
* @param originalClinicalDocumentValidationResult
* the original clinical document validation result
* @param segmentedClinicalDocumentValidationResult
* the segmented clinical document validation result
* @param isAuditFailureByPass
* the is audit failure by pass
* @throws AuditException
* the audit exception
*/
private void auditSegmentation(String originalDocument,
String segmentedDocument, XacmlResult xacmlResult,
RedactedDocument redactedDocument, String rulesFired,
XmlValidationResult originalClinicalDocumentValidationResult,
XmlValidationResult segmentedClinicalDocumentValidationResult,
boolean isAuditFailureByPass) throws AuditException {
final Map<PredicateKey, String> predicateMap = auditService
.createPredicateMap();
if (redactedDocument.getRedactedSectionSet().size() > 0) {
predicateMap.put(SECTION_OBLIGATIONS_APPLIED, redactedDocument
.getRedactedSectionSet().toString());
}
if (redactedDocument.getRedactedCategorySet().size() > 0) {
predicateMap.put(CATEGORY_OBLIGATIONS_APPLIED, redactedDocument
.getRedactedCategorySet().toString());
}
if (rulesFired != null) {
predicateMap.put(RULES_FIRED, rulesFired);
}
predicateMap.put(ORIGINAL_DOCUMENT, originalDocument);
predicateMap.put(SEGMENTED_DOCUMENT, segmentedDocument);
predicateMap.put(ORIGINAL_DOCUMENT_VALID, Boolean
.toString(originalClinicalDocumentValidationResult.isValid()));
predicateMap.put(SEGMENTED_DOCUMENT_VALID, Boolean
.toString(segmentedClinicalDocumentValidationResult.isValid()));
try {
auditService.audit(this, xacmlResult.getMessageId(),
SEGMENT_DOCUMENT, xacmlResult.getPatientId(), predicateMap);
} catch (final AuditException e) {
if (isAuditFailureByPass) {
// main flow should work though the audit service has some
// issues
logger.error("Audit Service is Down");
logger.debug("patient id" + xacmlResult.getPatientId());
logger.debug("original document" + originalDocument);
logger.debug("segmented document" + segmentedDocument);
// TODO send the email notification to core team
// Or send a notification using rabbitmq
} else {
// main flow shouldn't work if audit service has some issues
throw e;
}
}
}
/**
* Creates the xml validator.
*
* @return the xml validation
*/
private XmlValidation createXmlValidator() {
return new XmlValidation(this.getClass().getClassLoader()
.getResourceAsStream(C32_CDA_XSD_PATH + C32_CDA_XSD_NAME),
C32_CDA_XSD_PATH);
}
}