Impl CodeSystem $validate-code for R4 with basic test cases

This commit is contained in:
Frank Tao 2020-09-18 22:30:59 -04:00
parent 7f7e1f39bc
commit 64f2bae2e7
10 changed files with 762 additions and 99 deletions

View File

@ -47,6 +47,8 @@ public interface IFhirResourceDaoCodeSystem<T extends IBaseResource, CD, CC> ext
SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, CD theCodingA, CD theCodingB, IPrimitiveType<String> theVersion, RequestDetails theRequestDetails);
IValidationSupport.CodeValidationResult validateCode(IIdType theCodeSystemId, IPrimitiveType<String> theCodeSystemUrl, IPrimitiveType<String> theVersion, IPrimitiveType<String> theCode, IPrimitiveType<String> theDisplay, CD theCoding, CC theCodeableConcept, RequestDetails theRequestDetails);
class SubsumesResult {
private final ConceptSubsumptionOutcome myOutcome;

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
@ -56,6 +57,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Set;
import static ca.uhn.fhir.jpa.dao.FhirResourceDaoValueSetDstu2.toStringOrNull;
import static ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoValueSetDstu3.vsValidateCodeOptions;
import static ca.uhn.fhir.jpa.util.LogicUtil.multiXor;
import static org.apache.commons.lang3.StringUtils.isBlank;
@ -312,4 +314,13 @@ public class FhirResourceDaoValueSetDstu2 extends BaseHapiFhirResourceDao<ValueS
return myTerminologySvc.validateCode(vsValidateCodeOptions(), theId, toStringOrNull(theValueSetIdentifier), toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
}
//-- For CodeSystem, not sure we need it here or throw invalid operation exception, since there is no CodeSystem in dstu2
@Override
public CodeValidationResult validateCode(IIdType theCodeSystemId, IPrimitiveType<String> theCodeSystemUrl,
IPrimitiveType<String> theVersion, IPrimitiveType<String> theCode, IPrimitiveType<String> theDisplay,
CodingDt theCoding, CodeableConceptDt theCodeableConcept, RequestDetails theRequestDetails) {
return myTerminologySvc.codeSystemValidateCode(theCodeSystemId, toStringOrNull(theCodeSystemUrl), toStringOrNull(theVersion), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
}
}

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.dao.dstu3;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
@ -51,6 +52,8 @@ import java.util.Date;
import java.util.List;
import java.util.Set;
import static ca.uhn.fhir.jpa.dao.FhirResourceDaoValueSetDstu2.toStringOrNull;
import static ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoValueSetDstu3.vsValidateCodeOptions;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.hl7.fhir.convertors.conv30_40.CodeSystem30_40.convertCodeSystem;
@ -169,4 +172,12 @@ public class FhirResourceDaoCodeSystemDstu3 extends BaseHapiFhirResourceDao<Code
return retVal;
}
@Override
public CodeValidationResult validateCode(IIdType theCodeSystemId, IPrimitiveType<String> theCodeSystemUrl,
IPrimitiveType<String> theVersion, IPrimitiveType<String> theCode, IPrimitiveType<String> theDisplay,
Coding theCoding, CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) {
return myTerminologySvc.codeSystemValidateCode(theCodeSystemId, toStringOrNull(theCodeSystemUrl), toStringOrNull(theVersion), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
}
}

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
@ -50,6 +51,8 @@ import java.util.Date;
import java.util.List;
import java.util.Set;
import static ca.uhn.fhir.jpa.dao.FhirResourceDaoValueSetDstu2.toStringOrNull;
import static ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoValueSetDstu3.vsValidateCodeOptions;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao<CodeSystem> implements IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> {
@ -164,5 +167,13 @@ public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao<CodeSys
return retVal;
}
@Override
public CodeValidationResult validateCode(IIdType theCodeSystemId, IPrimitiveType<String> theCodeSystemUrl,
IPrimitiveType<String> theVersion, IPrimitiveType<String> theCode, IPrimitiveType<String> theDisplay,
Coding theCoding, CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) {
return myTerminologySvc.codeSystemValidateCode(theCodeSystemId, toStringOrNull(theCodeSystemUrl), toStringOrNull(theVersion), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
}
}

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.dao.r5;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
@ -51,6 +52,8 @@ import java.util.Date;
import java.util.List;
import java.util.Set;
import static ca.uhn.fhir.jpa.dao.FhirResourceDaoValueSetDstu2.toStringOrNull;
import static ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoValueSetDstu3.vsValidateCodeOptions;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class FhirResourceDaoCodeSystemR5 extends BaseHapiFhirResourceDao<CodeSystem> implements IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> {
@ -166,5 +169,11 @@ public class FhirResourceDaoCodeSystemR5 extends BaseHapiFhirResourceDao<CodeSys
return retVal;
}
@Override
public CodeValidationResult validateCode(IIdType theCodeSystemId, IPrimitiveType<String> theCodeSystemUrl,
IPrimitiveType<String> theVersion, IPrimitiveType<String> theCode, IPrimitiveType<String> theDisplay,
Coding theCoding, CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) {
return myTerminologySvc.codeSystemValidateCode(theCodeSystemId, toStringOrNull(theCodeSystemUrl), toStringOrNull(theVersion), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
}
}

View File

@ -1,5 +1,19 @@
package ca.uhn.fhir.jpa.provider.r4;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType;
/*
* #%L
* HAPI FHIR JPA Server
@ -23,20 +37,11 @@ package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.BaseJpaResourceProviderValueSetDstu2;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
public class BaseJpaResourceProviderCodeSystemR4 extends JpaResourceProviderR4<CodeSystem> {
@ -98,4 +103,36 @@ public class BaseJpaResourceProviderCodeSystemR4 extends JpaResourceProviderR4<C
endRequest(theServletRequest);
}
}
/**
* $validate-code operation
*/
@SuppressWarnings("unchecked")
@Operation(name = JpaConstants.OPERATION_VALIDATE_CODE, idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) UriType theCodeSystemUrl,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "version", min = 0, max = 1) UriType theVersion,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay,
@OperationParam(name = "coding", min = 0, max = 1) Coding theCoding,
@OperationParam(name = "codeableConcept", min = 0, max = 1) CodeableConcept theCodeableConcept,
RequestDetails theRequestDetails
) {
startRequest(theServletRequest);
try {
IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> dao = (IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept>) getDao();
IValidationSupport.CodeValidationResult result = dao.validateCode(theId, theCodeSystemUrl, theVersion, theCode, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
return (Parameters) BaseJpaResourceProviderValueSetDstu2.toValidateCodeResult(getContext(), result);
} finally {
endRequest(theServletRequest);
}
}
}

View File

@ -1,5 +1,99 @@
package ca.uhn.fhir.jpa.term;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNoneBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Fetch;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.lucene.index.Term;
import org.apache.lucene.queries.TermsQuery;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.RegexpQuery;
import org.apache.lucene.search.TermQuery;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.query.dsl.BooleanJunction;
import org.hibernate.search.query.dsl.QueryBuilder;
import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r4.model.codesystems.ConceptSubsumptionOutcome;
import org.quartz.JobExecutionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.NoRollbackRuleAttribute;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.support.TransactionTemplate;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Stopwatch;
/*
* #%L
* HAPI FHIR JPA Server
@ -85,94 +179,6 @@ import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.util.ValidateUtil;
import ca.uhn.fhir.util.VersionIndependentConcept;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Stopwatch;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.lucene.index.Term;
import org.apache.lucene.queries.TermsQuery;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.RegexpQuery;
import org.apache.lucene.search.TermQuery;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.query.dsl.BooleanJunction;
import org.hibernate.search.query.dsl.QueryBuilder;
import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r4.model.codesystems.ConceptSubsumptionOutcome;
import org.quartz.JobExecutionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.NoRollbackRuleAttribute;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNoneBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
public static final int DEFAULT_FETCH_SIZE = 250;
@ -2555,5 +2561,117 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return ourLastResultsFromTranslationWithReverseCache;
}
@Override
public CodeValidationResult codeSystemValidateCode(IIdType theCodeSystemId, String theCodeSystemUrl, String theVersion, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) {
CodeableConcept codeableConcept = toCanonicalCodeableConcept(theCodeableConcept);
boolean haveCodeableConcept = codeableConcept != null && codeableConcept.getCoding().size() > 0;
Coding coding = toCanonicalCoding(theCoding);
boolean haveCoding = coding != null && coding.isEmpty() == false;
boolean haveCode = theCode != null && theCode.isEmpty() == false;
if (!haveCodeableConcept && !haveCoding && !haveCode) {
throw new InvalidRequestException("No code, coding, or codeableConcept provided to validate.");
}
if (!LogicUtil.multiXor(haveCodeableConcept, haveCoding, haveCode)) {
throw new InvalidRequestException("$validate-code can only validate (code) OR (coding) OR (codeableConcept)");
}
boolean haveIdentifierParam = isNotBlank(theCodeSystemUrl);
String codeSystemUrl;
if (theCodeSystemId != null) {
IBaseResource codeSystem = myDaoRegistry.getResourceDao("CodeSystem").read(theCodeSystemId);
codeSystemUrl = CommonCodeSystemsTerminologyService.getCodeSystemUrl(codeSystem);
} else if (haveIdentifierParam) {
codeSystemUrl = theCodeSystemUrl;
} else {
throw new InvalidRequestException("Either CodeSystem ID or CodeSystem identifier must be provided. Unable to validate.");
}
String code = theCode;
String version = theVersion;
String display = theDisplay;
if (haveCodeableConcept) {
for (int i = 0; i < codeableConcept.getCoding().size(); i++) {
Coding nextCoding = codeableConcept.getCoding().get(i);
if (nextCoding.hasSystem()) {
if (!codeSystemUrl.equalsIgnoreCase(nextCoding.getSystem())) {
throw new InvalidRequestException("Coding.system '" + nextCoding.getSystem() + "' does not equal with CodeSystem.url '" + theCodeSystemUrl + "'. Unable to validate.");
}
codeSystemUrl = nextCoding.getSystem();
}
code = nextCoding.getCode();
display = nextCoding.getDisplay();
CodeValidationResult nextValidation = codeSystemValidateCode(codeSystemUrl, version, code, display);
if (nextValidation.isOk() || i == codeableConcept.getCoding().size() - 1) {
return nextValidation;
}
}
} else if (haveCoding) {
if (coding.hasSystem()) {
if (!codeSystemUrl.equalsIgnoreCase(coding.getSystem())) {
throw new InvalidRequestException("Coding.system '" + coding.getSystem() + "' does not equal with CodeSystem.url '" + theCodeSystemUrl + "'. Unable to validate.");
}
codeSystemUrl = coding.getSystem();
}
code = coding.getCode();
display = coding.getDisplay();
}
return codeSystemValidateCode(codeSystemUrl, version, code, display);
}
@SuppressWarnings("unchecked")
private CodeValidationResult codeSystemValidateCode(String theCodeSystemUrl, String theCodeSystemVersion, String theCode, String theDisplay) {
CriteriaBuilder criteriaBuilder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<TermConcept> query = criteriaBuilder.createQuery(TermConcept.class);
Root<TermConcept> root = query.from(TermConcept.class);
Fetch<TermCodeSystemVersion, TermConcept> systemVersionFetch = root.fetch("myCodeSystem", JoinType.INNER);
Join<TermCodeSystemVersion, TermConcept> systemVersionJoin = (Join<TermCodeSystemVersion, TermConcept>)systemVersionFetch;
Fetch<TermCodeSystem, TermCodeSystemVersion> systemFetch = systemVersionFetch.fetch("myCodeSystem", JoinType.INNER);
Join<TermCodeSystem, TermCodeSystemVersion> systemJoin = (Join<TermCodeSystem, TermCodeSystemVersion>)systemFetch;
ArrayList<Predicate> predicates = new ArrayList<>();
if (isNotBlank(theCode)) {
predicates.add(criteriaBuilder.equal(root.get("myCode"), theCode));
}
if (isNotBlank(theDisplay)) {
predicates.add(criteriaBuilder.equal(root.get("myDisplay"), theDisplay));
}
if (isNoneBlank(theCodeSystemUrl)) {
predicates.add(criteriaBuilder.equal(systemJoin.get("myCodeSystemUri"), theCodeSystemUrl));
}
if (isNoneBlank(theCodeSystemVersion)) {
predicates.add(criteriaBuilder.equal(systemVersionJoin.get("myCodeSystemVersionId"), theCodeSystemVersion));
}
Predicate outerPredicate = criteriaBuilder.and(predicates.toArray(new Predicate[0]));
query.where(outerPredicate);
final TypedQuery<TermConcept> typedQuery = myEntityManager.createQuery(query.select(root));
org.hibernate.query.Query<TermConcept> hibernateQuery = (org.hibernate.query.Query<TermConcept>) typedQuery;
hibernateQuery.setFetchSize(myFetchSize);
List<TermConcept> resultsList = hibernateQuery.getResultList();
if (!resultsList.isEmpty()) {
TermConcept concept = resultsList.get(0);
return new CodeValidationResult().setCode(concept.getCode()).setDisplay(concept.getDisplay());
}
if (isBlank(theDisplay))
return createFailureCodeValidationResult(theCodeSystemUrl, theCode);
else
return createFailureCodeValidationResult(theCodeSystemUrl, theCode, " - Concept Display : " + theDisplay);
}
}

View File

@ -126,4 +126,9 @@ public interface ITermReadSvc extends IValidationSupport {
*/
boolean isValueSetPreExpandedForCodeValidation(IBaseResource theValueSet);
/**
* Version independent
*/
CodeValidationResult codeSystemValidateCode(IIdType theCodeSystemId, String theValueSetUrl, String theVersion, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept);
}

View File

@ -0,0 +1,433 @@
package ca.uhn.fhir.jpa.provider.r4;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.IOException;
import javax.annotation.Nonnull;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class ResourceProviderR4CodeSystemValidationTest extends BaseResourceProviderR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderR4CodeSystemValidationTest.class);
private IIdType myCsId;
private static final String CS_ACMS_URL = "http://acme.org";
@BeforeEach
@Transactional
public void before02() throws IOException {
loadAndPersistCodeSystem();
}
private void loadAndPersistCodeSystem() throws IOException {
CodeSystem codeSystem = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs.xml");
codeSystem.setId("CodeSystem/cs");
persistCodeSystem(codeSystem);
}
private void persistCodeSystem(CodeSystem theCodeSystem) {
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) {
myCsId = myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless();
}
});
myCodeSystemDao.readEntity(myCsId, null).getId();
}
@Test
public void testValidateCodeFoundByCode() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("code").setValue(new CodeType("8452-5"));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeNotFoundByCode() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("code").setValue(new CodeType("8452-5-a"));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(false, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Unknown code {http://acme.org}8452-5-a", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodeMatchDisplay() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("code").setValue(new CodeType("8452-5"));
inParams.addParameter().setName("display").setValue(new CodeType("Systolic blood pressure.inspiration - expiration"));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodeNotMatchDisplay() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("code").setValue(new CodeType("8452-5"));
inParams.addParameter().setName("display").setValue(new CodeType("Old Systolic blood pressure.inspiration - expiration"));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(false, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Unknown code {http://acme.org}8452-5 - Concept Display : Old Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodeWithoutUrl() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("code").setValue(new CodeType("8452-5"));
try {
myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Either CodeSystem ID or CodeSystem identifier must be provided. Unable to validate.",e.getMessage());
}
}
@Test
public void testValidateCodeFoundByCodeWithId() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("code").setValue(new CodeType("8452-5"));
Parameters respParam = myClient.operation().onInstance(myCsId).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeWithoutCodeOrCodingOrCodeableConcept() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("display").setValue(new CodeType("Systolic blood pressure.inspiration - expiration"));
try {
myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: No code, coding, or codeableConcept provided to validate.",e.getMessage());
}
}
@Test
public void testValidateCodeWithCodeAndCoding() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("code").setValue(new CodeType("8452-5"));
inParams.addParameter().setName("coding").setValue((new Coding().setCode("8452-1")));
try {
myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: $validate-code can only validate (code) OR (coding) OR (codeableConcept)",e.getMessage());
}
}
@Test
public void testValidateCodeFoundByCodingWithUrlNotMatch() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("coding").setValue((new Coding().setCode("8452-5").setSystem("http://url2")));
try {
myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Coding.system 'http://url2' does not equal with CodeSystem.url 'http://acme.org'. Unable to validate.",e.getMessage());
}
}
@Test
public void testValidateCodeFoundByCoding() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("coding").setValue((new Coding().setCode("8452-5")));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodingWithSystem() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("coding").setValue((new Coding().setCode("8452-5").setSystem(CS_ACMS_URL)));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodingUrlNotMatch() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("coding").setValue((new Coding().setCode("8452-5").setSystem("http://url2")));
try {
myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Coding.system 'http://url2' does not equal with CodeSystem.url 'http://acme.org'. Unable to validate.",e.getMessage());
}
}
@Test
public void testValidateCodeFoundByCodingWithDisplay() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("coding").setValue((new Coding().setCode("8452-5").setDisplay("Systolic blood pressure.inspiration - expiration")));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeNotFoundByCoding() throws Exception {
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("coding").setValue((new Coding().setCode("8452-5-a")));
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(false, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Unknown code {http://acme.org}8452-5-a", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodeableConcept() throws Exception {
CodeableConcept cc = new CodeableConcept();
cc.addCoding().setCode("8452-5");
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("codeableConcept").setValue(cc);
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodeableConceptWithSystem() throws Exception {
CodeableConcept cc = new CodeableConcept();
cc.addCoding().setCode("8452-5").setSystem(CS_ACMS_URL);
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("codeableConcept").setValue(cc);
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodeableConceptWithDisplay() throws Exception {
CodeableConcept cc = new CodeableConcept();
cc.addCoding().setCode("8452-5").setSystem(CS_ACMS_URL).setDisplay("Systolic blood pressure.inspiration - expiration");
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("codeableConcept").setValue(cc);
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeNotFoundByCodeableConcept() throws Exception {
CodeableConcept cc = new CodeableConcept();
cc.addCoding().setCode("8452-5-a");
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("codeableConcept").setValue(cc);
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(false, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Unknown code {http://acme.org}8452-5-a", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodeableConceptUrlNotMatch() throws Exception {
CodeableConcept cc = new CodeableConcept();
cc.addCoding().setCode("8452-5").setSystem("http://url2").setDisplay("Systolic blood pressure.inspiration - expiration");
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("codeableConcept").setValue(cc);
try {
myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Coding.system 'http://url2' does not equal with CodeSystem.url 'http://acme.org'. Unable to validate.",e.getMessage());
}
}
@Test
public void testValidateCodeFoundByCodeableConceptWithMultipleMatchedEntries() throws Exception {
CodeableConcept cc = new CodeableConcept();
cc.addCoding().setCode("8452-5").setSystem(CS_ACMS_URL).setDisplay("Systolic blood pressure.inspiration - expiration");
cc.addCoding().setCode("8451-7").setSystem(CS_ACMS_URL).setDisplay("Systolic blood pressure--inspiration");
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("codeableConcept").setValue(cc);
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodeableConceptWithMultipleMatchedFirstEntry() throws Exception {
CodeableConcept cc = new CodeableConcept();
cc.addCoding().setCode("8452-5").setSystem(CS_ACMS_URL).setDisplay("Systolic blood pressure.inspiration - expiration");
cc.addCoding().setCode("8451-7-a").setSystem(CS_ACMS_URL).setDisplay("Systolic blood pressure--inspiration");
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("codeableConcept").setValue(cc);
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
public void testValidateCodeFoundByCodeableConceptWithMultipleMatchedSecondEntry() throws Exception {
CodeableConcept cc = new CodeableConcept();
cc.addCoding().setCode("8452-5-a").setSystem(CS_ACMS_URL).setDisplay("Systolic blood pressure.inspiration - expiration");
cc.addCoding().setCode("8451-7").setSystem(CS_ACMS_URL).setDisplay("Systolic blood pressure--inspiration");
Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CS_ACMS_URL));
inParams.addParameter().setName("codeableConcept").setValue(cc);
Parameters respParam = myClient.operation().onType(CodeSystem.class).named("validate-code").withParameters(inParams).execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Systolic blood pressure--inspiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
}

View File

@ -332,6 +332,32 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport {
return url;
}
public static String getCodeSystemUrl(@Nonnull IBaseResource theCodeSystem) {
String url;
switch (theCodeSystem.getStructureFhirVersionEnum()) {
case DSTU2_HL7ORG: {
url = ((CodeSystem) theCodeSystem).getUrl();
break;
}
case DSTU3: {
url = ((org.hl7.fhir.dstu3.model.CodeSystem) theCodeSystem).getUrl();
break;
}
case R4: {
url = ((org.hl7.fhir.r4.model.CodeSystem) theCodeSystem).getUrl();
break;
}
case R5: {
url = ((org.hl7.fhir.r5.model.CodeSystem) theCodeSystem).getUrl();
break;
}
case DSTU2:
case DSTU2_1:
default:
throw new IllegalArgumentException("Can not handle version: " + theCodeSystem.getStructureFhirVersionEnum());
}
return url;
}
private static HashMap<String, String> buildUspsCodes() {
HashMap<String, String> uspsCodes = new HashMap<>();
uspsCodes.put("AK", "Alaska");