package edu.gatech.i3l.fhir.jpa.dao;
import java.util.Set;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import org.apache.commons.lang3.StringUtils;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu2.valueset.IssueSeverityEnum;
import ca.uhn.fhir.model.dstu2.valueset.IssueTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import edu.gatech.i3l.fhir.dstu2.entities.OmopMeasurement;
import edu.gatech.i3l.fhir.dstu2.entities.OmopObservation;
import edu.gatech.i3l.fhir.jpa.entity.BaseResourceEntity;
import edu.gatech.i3l.fhir.jpa.entity.IResourceEntity;
import edu.gatech.i3l.fhir.jpa.query.AbstractPredicateBuilder;
import edu.gatech.i3l.fhir.jpa.query.PredicateBuilder;
import edu.gatech.i3l.fhir.jpa.util.StopWatch;
import edu.gatech.i3l.omop.mapping.OmopConceptMapping;
import net.vidageek.mirror.dsl.Mirror;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
@Transactional(propagation = Propagation.REQUIRED)
public class ObservationFhirResourceDao extends BaseFhirResourceDao<Observation>{
// Observation FHIR mapping to OMOP v5 requires accessing two different tables in OMOP v5.
// To support C U and D from CRUD operation, we have to overwrite the base FHIR resource DAO methods
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseFhirResourceDao.class);
/* (non-Javadoc)
* @see edu.gatech.i3l.fhir.jpa.dao.BaseFhirResourceDao#create(ca.uhn.fhir.model.api.IResource, java.lang.String, boolean)
*/
@Override
public DaoMethodOutcome create(Observation theResource, String theIfNoneExist, boolean thePerformIndexing) {
// TODO Auto-generated method stub
System.out.println("ObservationFhirResourceDao create");
return observationCreate(theResource, theIfNoneExist, thePerformIndexing);
}
private DaoMethodOutcome observationCreate(Observation theResource, String theIfNoneExist, boolean thePerformIndexing) {
Validator myBeanValidator = getBeanValidator();
boolean myValidateBean = isValidateBean();
FhirContext myContext = getContext();
BaseFhirDao baseFhirDao = getBaseFhirDao();
Class<Observation> myResourceType = getResourceType();
OmopConceptMapping ocm = OmopConceptMapping.getInstance();
Class<? extends IResourceEntity> myEntityClass;
StopWatch w = new StopWatch();
String code = theResource.getCode().getCodingFirstRep().getCode();
String domain = ocm.getDomain(code);
if (domain.equalsIgnoreCase("measurement"))
myEntityClass = edu.gatech.i3l.fhir.dstu2.entities.OmopMeasurement.class;
else if (domain.equalsIgnoreCase("observation"))
myEntityClass = edu.gatech.i3l.fhir.dstu2.entities.OmopObservation.class;
else {
OperationOutcome oo = new OperationOutcome();
oo.addIssue().setSeverity(IssueSeverityEnum.ERROR).setCode(IssueTypeEnum.INVALID_CODE).setDetails((new CodeableConceptDt()).setText("Coding System Not Supported. We support Measurement or Observation domain code in OMOP v5"));
throw new UnprocessableEntityException(myContext, oo);
}
BaseResourceEntity entity = (BaseResourceEntity) new Mirror().on(myEntityClass).invoke().constructor().withoutArgs();
entity.constructEntityFromResource(theResource);
if(myValidateBean){
Set<ConstraintViolation<BaseResourceEntity>> violations = myBeanValidator.validate(entity);
if(!violations.isEmpty()){
OperationOutcome oo = new OperationOutcome();
for (ConstraintViolation<BaseResourceEntity> violation : violations) {
oo.addIssue().setSeverity(IssueSeverityEnum.ERROR).setCode(IssueTypeEnum.PROCESSING_FAILURE).setDetails((new CodeableConceptDt()).setText(violation.getPropertyPath()+" "+ violation.getMessage()));
}
throw new UnprocessableEntityException(myContext, oo);
}
}
baseFhirDao.updateEntity(theResource, entity, false, null, thePerformIndexing, true);
DaoMethodOutcome outcome = toMethodOutcome(entity, theResource).setCreated(true);
baseFhirDao.notifyWriteCompleted();
ourLog.info("Processed create on {} in {}ms", myResourceType, w.getMillisAndRestart());
return outcome;
}
private DaoMethodOutcome toMethodOutcome(final BaseResourceEntity entity, IResource theResource) {
DaoMethodOutcome outcome = new DaoMethodOutcome();
outcome.setId(new IdDt(entity.getId()));
outcome.setEntity(entity);
outcome.setResource(theResource);
if (theResource != null) {
theResource.setId(new IdDt(entity.getId()));
}
return outcome;
}
public ObservationFhirResourceDao() {
super();
setResourceEntity(edu.gatech.i3l.fhir.dstu2.entities.Observation.class); //TODO set this automatically; this is error prone since we need to remember to set this on each dao class
setValidateBean(true);
}
@Override
public PredicateBuilder getPredicateBuilder() {
return new AbstractPredicateBuilder() {
private static final String LOINC = "loinc";
private static final String SNOMED = "snomed";
private static final String ICD_9 = "icd-9";
private static final String ICD_9_CM = "icd-9-cm";
private static final String ICD_9_PROC = "icd-9-proc";
private static final String RXNORM = "rxnorm";
private static final String UCUM = "ucum";
private static final String ICD_10 = "icd-10";
@Override
public Predicate addCommonPredicate(CriteriaBuilder builder, From<? extends IResourceEntity, ? extends IResourceEntity> from) {
// builder.asc(from.get("id"));
return builder.notEqual(from.get("observationConcept").get("id"), edu.gatech.i3l.fhir.dstu2.entities.Observation.DIASTOLIC_CONCEPT_ID);
//In Omop database, the dictionary is static; that means we can reference id's directly: the id for the vocabulary RxNorm is 8
}
@Override
public Predicate translatePredicateTokenSystem(Class<? extends IResourceEntity> entity, String theParamName, String system, From<? extends IResourceEntity, ? extends IResourceEntity> from,
CriteriaBuilder theBuilder) {
Predicate predicate = null;
if (system == null) {
return null;
}
system = getVocabularyName(system);
Path<Object> path = null;
switch (theParamName) {
case Observation.SP_CODE:
path = from.get("observationConcept").get("vocabulary").get("id");
break;
default:
break;
}
if (StringUtils.isNotBlank(system)) {
predicate = theBuilder.like(path.as(String.class), system+"%");
}// else {
// return theBuilder.isNull(path); //WARNING originally, if the system is empty, then it would be checked for null systems
// }
return predicate;
}
private String getVocabularyName(String system) {
if(system.contains(SNOMED)){
return "SNOMED";
} else if (system.contains(LOINC)){
return "LOINC";
} else if (system.contains(ICD_10)){
return "ICD10";
} else if (system.contains(ICD_9)){
return "ICD9CM";
} else if (system.contains(ICD_9_CM)){
return "ICD9CM";
} else if (system.contains(ICD_9_PROC)){
return "ICD9Proc";
} else if (system.contains(RXNORM)){
return "RxNorm";
} else if (system.contains(UCUM)){
return "UCUM";
}
return "";
}
@Override
public Predicate translatePredicateTokenCode(Class<? extends IResourceEntity> entity, String theParamName, String code, From<? extends IResourceEntity, ? extends IResourceEntity> from,
CriteriaBuilder theBuilder) {
Predicate predicate = null;
Path<Object> path = null;
switch (theParamName) {
case Observation.SP_CODE:
path = from.get("observationConcept").get("conceptCode");
break;
default:
break;
}
if (StringUtils.isNotBlank(code)) {
predicate = theBuilder.equal(path, code);
} //else {
// return theBuilder.isNull(path);
// }
return predicate;
}
};
}
}