Always apply vs filters correctly (#2195)

* Always apply valueset filters correctly

* Test fix

* Testfix

* Test fix

* Add changelog

* Test fix
This commit is contained in:
James Agnew 2020-11-24 18:12:02 -05:00 committed by GitHub
parent 702cc7762d
commit fb8658da70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 427 additions and 736 deletions

View File

@ -93,4 +93,10 @@ public class ValueSetExpansionOptions {
myFailOnMissingCodeSystem = theFailOnMissingCodeSystem;
return this;
}
public static ValueSetExpansionOptions forOffsetAndCount(int theOffset, int theCount) {
return new ValueSetExpansionOptions()
.setOffset(theOffset)
.setCount(theCount);
}
}

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 2195
title: "When performing a ValueSet expansion with a filter a large pre-expanded
ValueSet (more than 1000 codes), the filter failed to find concepts appearing
after the first thousand. This has been corrected."

View File

@ -34,7 +34,7 @@ public interface ITermValueSetConceptViewDao extends JpaRepository<TermValueSetC
@Query("SELECT v FROM TermValueSetConceptView v WHERE v.myConceptValueSetPid = :pid AND v.myConceptOrder >= :from AND v.myConceptOrder < :to ORDER BY v.myConceptOrder")
List<TermValueSetConceptView> findByTermValueSetId(@Param("from") int theFrom, @Param("to") int theTo, @Param("pid") Long theValueSetId);
@Query("SELECT v FROM TermValueSetConceptView v WHERE v.myConceptValueSetPid = :pid AND v.myConceptDisplay LIKE :display ORDER BY v.myConceptOrder")
@Query("SELECT v FROM TermValueSetConceptView v WHERE v.myConceptValueSetPid = :pid AND LOWER(v.myConceptDisplay) LIKE :display ORDER BY v.myConceptOrder")
List<TermValueSetConceptView> findByTermValueSetId(@Param("pid") Long theValueSetId, @Param("display") String theDisplay);
}

View File

@ -22,7 +22,6 @@ package ca.uhn.fhir.jpa.dao.dstu3;
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
@ -31,216 +30,60 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.ElementUtil;
import org.hl7.fhir.convertors.conv30_40.ValueSet30_40;
import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.IntegerType;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetFilterComponent;
import org.hl7.fhir.dstu3.model.ValueSet.FilterOperator;
import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.exceptions.FHIRException;
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 java.util.Date;
import java.util.List;
import static ca.uhn.fhir.jpa.dao.FhirResourceDaoValueSetDstu2.toStringOrNull;
import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoValueSetR4.validateHaveExpansionOrThrowInternalErrorException;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.hl7.fhir.convertors.conv30_40.ValueSet30_40.convertValueSet;
public class FhirResourceDaoValueSetDstu3 extends BaseHapiFhirResourceDao<ValueSet> implements IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> {
private IValidationSupport myValidationSupport;
@Override
public void start() {
super.start();
myValidationSupport = getApplicationContext().getBean(IValidationSupport.class, "myJpaValidationSupportChain");
}
@Override
public ValueSet expand(IIdType theId, String theFilter, RequestDetails theRequestDetails) {
ValueSet source = read(theId, theRequestDetails);
public org.hl7.fhir.dstu3.model.ValueSet expand(IIdType theId, String theFilter, RequestDetails theRequestDetails) {
org.hl7.fhir.dstu3.model.ValueSet source = read(theId, theRequestDetails);
return expand(source, theFilter);
}
@Override
public ValueSet expand(IIdType theId, String theFilter, int theOffset, int theCount, RequestDetails theRequestDetails) {
ValueSet source = read(theId, theRequestDetails);
public org.hl7.fhir.dstu3.model.ValueSet expand(IIdType theId, String theFilter, int theOffset, int theCount, RequestDetails theRequestDetails) {
org.hl7.fhir.dstu3.model.ValueSet source = read(theId, theRequestDetails);
return expand(source, theFilter, theOffset, theCount);
}
private ValueSet doExpand(ValueSet theSource) {
validateIncludes("include", theSource.getCompose().getInclude());
validateIncludes("exclude", theSource.getCompose().getExclude());
IValidationSupport.ValueSetExpansionOutcome retVal = myValidationSupport.expandValueSet(new ValidationSupportContext(myValidationSupport), null, theSource);
validateHaveExpansionOrThrowInternalErrorException(retVal);
return (ValueSet) retVal.getValueSet();
}
private ValueSet doExpand(ValueSet theSource, int theOffset, int theCount) {
validateIncludes("include", theSource.getCompose().getInclude());
validateIncludes("exclude", theSource.getCompose().getExclude());
ValueSetExpansionOptions options = new ValueSetExpansionOptions()
.setOffset(theOffset)
.setCount(theCount);
IValidationSupport.ValueSetExpansionOutcome retVal = myValidationSupport.expandValueSet(new ValidationSupportContext(myValidationSupport), options, theSource);
validateHaveExpansionOrThrowInternalErrorException(retVal);
return (ValueSet) retVal.getValueSet();
}
private void validateIncludes(String name, List<ConceptSetComponent> listToValidate) {
for (ConceptSetComponent nextExclude : listToValidate) {
if (isBlank(nextExclude.getSystem()) && nextExclude.getValueSet().isEmpty() && !ElementUtil.isEmpty(nextExclude.getConcept(), nextExclude.getFilter())) {
throw new InvalidRequestException("ValueSet contains " + name + " criteria with no system defined");
}
}
@Override
public org.hl7.fhir.dstu3.model.ValueSet expandByIdentifier(String theUri, String theFilter) {
org.hl7.fhir.r4.model.ValueSet canonicalOutput = myTerminologySvc.expandValueSet(null, theUri, theFilter);
return ValueSet30_40.convertValueSet(canonicalOutput);
}
@Override
public ValueSet expandByIdentifier(String theUri, String theFilter) {
if (isBlank(theUri)) {
throw new InvalidRequestException("URI must not be blank or missing");
}
ValueSet source = new ValueSet();
source.setUrl(theUri);
if (isNotBlank(theFilter)) {
ConceptSetFilterComponent filter = source.getCompose().addInclude().addValueSet(theUri).addFilter();
filter.setProperty("display");
filter.setOp(FilterOperator.EQUAL);
filter.setValue(theFilter);
} else {
source.getCompose().addInclude().addValueSet(theUri);
}
return doExpand(source);
// if (defaultValueSet != null) {
// source = getContext().newJsonParser().parseResource(ValueSet.class, getContext().newJsonParser().encodeResourceToString(defaultValueSet));
// } else {
// IBundleProvider ids = search(ValueSet.SP_URL, new UriParam(theUri));
// if (ids.size() == 0) {
// throw new InvalidRequestException("Unknown ValueSet URI: " + theUri);
// }
// source = (ValueSet) ids.getResources(0, 1).get(0);
// }
//
// return expand(defaultValueSet, theFilter);
public org.hl7.fhir.dstu3.model.ValueSet expandByIdentifier(String theUri, String theFilter, int theOffset, int theCount) {
ValueSetExpansionOptions options = ValueSetExpansionOptions.forOffsetAndCount(theOffset, theCount);
org.hl7.fhir.r4.model.ValueSet canonicalOutput = myTerminologySvc.expandValueSet(options, theUri, theFilter);
return ValueSet30_40.convertValueSet(canonicalOutput);
}
@Override
public ValueSet expandByIdentifier(String theUri, String theFilter, int theOffset, int theCount) {
if (isBlank(theUri)) {
throw new InvalidRequestException("URI must not be blank or missing");
}
ValueSet source = new ValueSet();
source.setUrl(theUri);
if (isNotBlank(theFilter)) {
ConceptSetFilterComponent filter = source.getCompose().addInclude().addValueSet(theUri).addFilter();
filter.setProperty("display");
filter.setOp(FilterOperator.EQUAL);
filter.setValue(theFilter);
} else {
source.getCompose().addInclude().addValueSet(theUri);
}
return doExpand(source, theOffset, theCount);
public org.hl7.fhir.dstu3.model.ValueSet expand(org.hl7.fhir.dstu3.model.ValueSet theSource, String theFilter) {
org.hl7.fhir.r4.model.ValueSet canonicalInput = ValueSet30_40.convertValueSet(theSource);
org.hl7.fhir.r4.model.ValueSet canonicalOutput = myTerminologySvc.expandValueSet(null, canonicalInput, theFilter);
return ValueSet30_40.convertValueSet(canonicalOutput);
}
@Override
public ValueSet expand(ValueSet theSource, String theFilter) {
ValueSet toExpand = new ValueSet();
// for (UriType next : theSource.getCompose().getInclude()) {
// ConceptSetComponent include = toExpand.getCompose().addInclude();
// include.setSystem(next.getValue());
// addFilterIfPresent(theFilter, include);
// }
for (ConceptSetComponent next : theSource.getCompose().getInclude()) {
toExpand.getCompose().addInclude(next);
addFilterIfPresent(theFilter, next);
}
if (toExpand.getCompose().isEmpty()) {
throw new InvalidRequestException("ValueSet does not have any compose.include or compose.import values, can not expand");
}
toExpand.getCompose().getExclude().addAll(theSource.getCompose().getExclude());
ValueSet retVal = doExpand(toExpand);
if (isNotBlank(theFilter)) {
applyFilter(retVal.getExpansion().getTotalElement(), retVal.getExpansion().getContains(), theFilter);
}
return retVal;
}
@Override
public ValueSet expand(ValueSet theSource, String theFilter, int theOffset, int theCount) {
ValueSet toExpand = new ValueSet();
toExpand.setId(theSource.getId());
toExpand.setUrl(theSource.getUrl());
if (theSource.getVersion() != null) {
toExpand.setVersion(theSource.getVersion());
}
for (ConceptSetComponent next : theSource.getCompose().getInclude()) {
toExpand.getCompose().addInclude(next);
addFilterIfPresent(theFilter, next);
}
if (toExpand.getCompose().isEmpty()) {
throw new InvalidRequestException("ValueSet does not have any compose.include or compose.import values, can not expand");
}
toExpand.getCompose().getExclude().addAll(theSource.getCompose().getExclude());
ValueSet retVal = doExpand(toExpand, theOffset, theCount);
if (isNotBlank(theFilter)) {
applyFilter(retVal.getExpansion().getTotalElement(), retVal.getExpansion().getContains(), theFilter);
}
return retVal;
}
private void applyFilter(IntegerType theTotalElement, List<ValueSetExpansionContainsComponent> theContains, String theFilter) {
for (int idx = 0; idx < theContains.size(); idx++) {
ValueSetExpansionContainsComponent next = theContains.get(idx);
if (isBlank(next.getDisplay()) || !org.apache.commons.lang3.StringUtils.containsIgnoreCase(next.getDisplay(), theFilter)) {
theContains.remove(idx);
idx--;
if (theTotalElement.getValue() != null) {
theTotalElement.setValue(theTotalElement.getValue() - 1);
}
}
applyFilter(theTotalElement, next.getContains(), theFilter);
}
}
private void addFilterIfPresent(String theFilter, ConceptSetComponent include) {
if (ElementUtil.isEmpty(include.getConcept())) {
if (isNotBlank(theFilter)) {
include.addFilter().setProperty("display").setOp(FilterOperator.EQUAL).setValue(theFilter);
}
}
public org.hl7.fhir.dstu3.model.ValueSet expand(org.hl7.fhir.dstu3.model.ValueSet theSource, String theFilter, int theOffset, int theCount) {
ValueSetExpansionOptions options = ValueSetExpansionOptions.forOffsetAndCount(theOffset, theCount);
org.hl7.fhir.r4.model.ValueSet canonicalInput = ValueSet30_40.convertValueSet(theSource);
org.hl7.fhir.r4.model.ValueSet canonicalOutput = myTerminologySvc.expandValueSet(options, canonicalInput, theFilter);
return ValueSet30_40.convertValueSet(canonicalOutput);
}
@Override

View File

@ -205,7 +205,7 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
*/
if (modifier == TokenParamModifier.IN) {
codes.addAll(myTerminologySvc.expandValueSet(null, code));
codes.addAll(myTerminologySvc.expandValueSetIntoConceptList(null, code));
} else if (modifier == TokenParamModifier.ABOVE) {
system = determineSystemIfMissing(theSearchParam, code, system);
validateHaveSystemAndCodeForToken(paramName, code, system);
@ -273,7 +273,7 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu
String valueSet = valueSetUris.iterator().next();
ValueSetExpansionOptions options = new ValueSetExpansionOptions()
.setFailOnMissingCodeSystem(false);
List<FhirVersionIndependentConcept> candidateCodes = myTerminologySvc.expandValueSet(options, valueSet);
List<FhirVersionIndependentConcept> candidateCodes = myTerminologySvc.expandValueSetIntoConceptList(options, valueSet);
for (FhirVersionIndependentConcept nextCandidate : candidateCodes) {
if (nextCandidate.getCode().equals(code)) {
retVal = nextCandidate.getSystem();

View File

@ -21,48 +21,28 @@ package ca.uhn.fhir.jpa.dao.r4;
*/
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.ElementUtil;
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.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r4.model.ValueSet.ConceptSetFilterComponent;
import org.hl7.fhir.r4.model.ValueSet.FilterOperator;
import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent;
import java.util.Date;
import java.util.List;
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.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class FhirResourceDaoValueSetR4 extends BaseHapiFhirResourceDao<ValueSet> implements IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> {
private IValidationSupport myValidationSupport;
@Override
public void start() {
super.start();
myValidationSupport = getApplicationContext().getBean(IValidationSupport.class, "myJpaValidationSupportChain");
}
@Override
public ValueSet expand(IIdType theId, String theFilter, RequestDetails theRequestDetails) {
ValueSet source = read(theId, theRequestDetails);
@ -75,156 +55,26 @@ public class FhirResourceDaoValueSetR4 extends BaseHapiFhirResourceDao<ValueSet>
return expand(source, theFilter, theOffset, theCount);
}
private ValueSet doExpand(ValueSet theSource) {
IValidationSupport.ValueSetExpansionOutcome retVal = myValidationSupport.expandValueSet(new ValidationSupportContext(myValidationSupport), null, theSource);
validateHaveExpansionOrThrowInternalErrorException(retVal);
return (ValueSet) retVal.getValueSet();
}
private ValueSet doExpand(ValueSet theSource, int theOffset, int theCount) {
ValueSetExpansionOptions options = new ValueSetExpansionOptions()
.setOffset(theOffset)
.setCount(theCount);
IValidationSupport.ValueSetExpansionOutcome retVal = myValidationSupport.expandValueSet(new ValidationSupportContext(myValidationSupport), options, theSource);
validateHaveExpansionOrThrowInternalErrorException(retVal);
return (ValueSet) retVal.getValueSet();
}
@Override
public ValueSet expandByIdentifier(String theUri, String theFilter) {
if (isBlank(theUri)) {
throw new InvalidRequestException("URI must not be blank or missing");
}
ValueSet source = new ValueSet();
source.setUrl(theUri);
if (isNotBlank(theFilter)) {
ConceptSetFilterComponent filter = source.getCompose().addInclude().addValueSet(theUri).addFilter();
filter.setProperty("display");
filter.setOp(FilterOperator.EQUAL);
filter.setValue(theFilter);
} else {
source.getCompose().addInclude().addValueSet(theUri);
}
return doExpand(source);
// if (defaultValueSet != null) {
// source = getContext().newJsonParser().parseResource(ValueSet.class, getContext().newJsonParser().encodeResourceToString(defaultValueSet));
// } else {
// IBundleProvider ids = search(ValueSet.SP_URL, new UriParam(theUri));
// if (ids.size() == 0) {
// throw new InvalidRequestException("Unknown ValueSet URI: " + theUri);
// }
// source = (ValueSet) ids.getResources(0, 1).get(0);
// }
//
// return expand(defaultValueSet, theFilter);
return myTerminologySvc.expandValueSet(null, theUri, theFilter);
}
@Override
public ValueSet expandByIdentifier(String theUri, String theFilter, int theOffset, int theCount) {
if (isBlank(theUri)) {
throw new InvalidRequestException("URI must not be blank or missing");
}
ValueSet source = new ValueSet();
source.setUrl(theUri);
if (isNotBlank(theFilter)) {
ConceptSetFilterComponent filter = source.getCompose().addInclude().addValueSet(theUri).addFilter();
filter.setProperty("display");
filter.setOp(FilterOperator.EQUAL);
filter.setValue(theFilter);
} else {
source.getCompose().addInclude().addValueSet(theUri);
}
return doExpand(source, theOffset, theCount);
ValueSetExpansionOptions options = ValueSetExpansionOptions.forOffsetAndCount(theOffset, theCount);
return myTerminologySvc.expandValueSet(options, theUri, theFilter);
}
@Override
public ValueSet expand(ValueSet theSource, String theFilter) {
ValueSet toExpand = new ValueSet();
// for (UriType next : theSource.getCompose().getInclude()) {
// ConceptSetComponent include = toExpand.getCompose().addInclude();
// include.setSystem(next.getValue());
// addFilterIfPresent(theFilter, include);
// }
for (ConceptSetComponent next : theSource.getCompose().getInclude()) {
toExpand.getCompose().addInclude(next);
addFilterIfPresent(theFilter, next);
}
if (toExpand.getCompose().isEmpty()) {
throw new InvalidRequestException("ValueSet does not have any compose.include or compose.import values, can not expand");
}
toExpand.getCompose().getExclude().addAll(theSource.getCompose().getExclude());
ValueSet retVal = doExpand(toExpand);
if (isNotBlank(theFilter)) {
applyFilter(retVal.getExpansion().getTotalElement(), retVal.getExpansion().getContains(), theFilter);
}
return retVal;
return myTerminologySvc.expandValueSet(null, theSource, theFilter);
}
@Override
public ValueSet expand(ValueSet theSource, String theFilter, int theOffset, int theCount) {
ValueSet toExpand = new ValueSet();
toExpand.setId(theSource.getId());
toExpand.setUrl(theSource.getUrl());
if (theSource.getVersion() != null) {
toExpand.setVersion(theSource.getVersion());
}
for (ConceptSetComponent next : theSource.getCompose().getInclude()) {
toExpand.getCompose().addInclude(next);
addFilterIfPresent(theFilter, next);
}
if (toExpand.getCompose().isEmpty()) {
throw new InvalidRequestException("ValueSet does not have any compose.include or compose.import values, can not expand");
}
toExpand.getCompose().getExclude().addAll(theSource.getCompose().getExclude());
ValueSet retVal = doExpand(toExpand, theOffset, theCount);
if (isNotBlank(theFilter)) {
applyFilter(retVal.getExpansion().getTotalElement(), retVal.getExpansion().getContains(), theFilter);
}
return retVal;
}
private void applyFilter(IntegerType theTotalElement, List<ValueSetExpansionContainsComponent> theContains, String theFilter) {
for (int idx = 0; idx < theContains.size(); idx++) {
ValueSetExpansionContainsComponent next = theContains.get(idx);
if (isBlank(next.getDisplay()) || !org.apache.commons.lang3.StringUtils.containsIgnoreCase(next.getDisplay(), theFilter)) {
theContains.remove(idx);
idx--;
if (theTotalElement.getValue() != null) {
theTotalElement.setValue(theTotalElement.getValue() - 1);
}
}
applyFilter(theTotalElement, next.getContains(), theFilter);
}
}
private void addFilterIfPresent(String theFilter, ConceptSetComponent include) {
if (ElementUtil.isEmpty(include.getConcept())) {
if (isNotBlank(theFilter)) {
include.addFilter().setProperty(JpaConstants.VALUESET_FILTER_DISPLAY).setOp(FilterOperator.EQUAL).setValue(theFilter);
}
}
ValueSetExpansionOptions options = ValueSetExpansionOptions.forOffsetAndCount(theOffset, theCount);
return myTerminologySvc.expandValueSet(options, theSource, theFilter);
}
@Override

View File

@ -21,47 +21,28 @@ package ca.uhn.fhir.jpa.dao.r5;
*/
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.ElementUtil;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import org.hl7.fhir.convertors.conv40_50.ValueSet40_50;
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.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.Enumerations;
import org.hl7.fhir.r5.model.IntegerType;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
import java.util.Date;
import java.util.List;
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.dao.r4.FhirResourceDaoValueSetR4.validateHaveExpansionOrThrowInternalErrorException;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class FhirResourceDaoValueSetR5 extends BaseHapiFhirResourceDao<ValueSet> implements IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> {
private IValidationSupport myValidationSupport;
@Override
public void start() {
super.start();
myValidationSupport = getApplicationContext().getBean(IValidationSupport.class,"myJpaValidationSupportChain" );
}
@Override
public ValueSet expand(IIdType theId, String theFilter, RequestDetails theRequestDetails) {
ValueSet source = read(theId, theRequestDetails);
@ -74,157 +55,32 @@ public class FhirResourceDaoValueSetR5 extends BaseHapiFhirResourceDao<ValueSet>
return expand(source, theFilter, theOffset, theCount);
}
private ValueSet doExpand(ValueSet theSource) {
IValidationSupport.ValueSetExpansionOutcome retVal = myValidationSupport.expandValueSet(new ValidationSupportContext(myValidationSupport), null, theSource);
validateHaveExpansionOrThrowInternalErrorException(retVal);
return (ValueSet) retVal.getValueSet();
}
private ValueSet doExpand(ValueSet theSource, int theOffset, int theCount) {
ValueSetExpansionOptions options = new ValueSetExpansionOptions()
.setOffset(theOffset)
.setCount(theCount);
IValidationSupport.ValueSetExpansionOutcome retVal = myValidationSupport.expandValueSet(new ValidationSupportContext(myValidationSupport), options, theSource);
validateHaveExpansionOrThrowInternalErrorException(retVal);
return (ValueSet) retVal.getValueSet();
}
@Override
public ValueSet expandByIdentifier(String theUri, String theFilter) {
if (isBlank(theUri)) {
throw new InvalidRequestException("URI must not be blank or missing");
}
ValueSet source = new ValueSet();
source.setUrl(theUri);
if (isNotBlank(theFilter)) {
ConceptSetFilterComponent filter = source.getCompose().addInclude().addValueSet(theUri).addFilter();
filter.setProperty("display");
filter.setOp(Enumerations.FilterOperator.EQUAL);
filter.setValue(theFilter);
} else {
source.getCompose().addInclude().addValueSet(theUri);
}
ValueSet retVal = doExpand(source);
return retVal;
// if (defaultValueSet != null) {
// source = getContext().newJsonParser().parseResource(ValueSet.class, getContext().newJsonParser().encodeResourceToString(defaultValueSet));
// } else {
// IBundleProvider ids = search(ValueSet.SP_URL, new UriParam(theUri));
// if (ids.size() == 0) {
// throw new InvalidRequestException("Unknown ValueSet URI: " + theUri);
// }
// source = (ValueSet) ids.getResources(0, 1).get(0);
// }
//
// return expand(defaultValueSet, theFilter);
org.hl7.fhir.r4.model.ValueSet canonicalOutput = myTerminologySvc.expandValueSet(null, theUri, theFilter);
return ValueSet40_50.convertValueSet(canonicalOutput);
}
@Override
public ValueSet expandByIdentifier(String theUri, String theFilter, int theOffset, int theCount) {
if (isBlank(theUri)) {
throw new InvalidRequestException("URI must not be blank or missing");
}
ValueSet source = new ValueSet();
source.setUrl(theUri);
if (isNotBlank(theFilter)) {
ConceptSetFilterComponent filter = source.getCompose().addInclude().addValueSet(theUri).addFilter();
filter.setProperty("display");
filter.setOp(Enumerations.FilterOperator.EQUAL);
filter.setValue(theFilter);
} else {
source.getCompose().addInclude().addValueSet(theUri);
}
return doExpand(source, theOffset, theCount);
ValueSetExpansionOptions options = ValueSetExpansionOptions.forOffsetAndCount(theOffset, theCount);
org.hl7.fhir.r4.model.ValueSet canonicalOutput = myTerminologySvc.expandValueSet(options, theUri, theFilter);
return ValueSet40_50.convertValueSet(canonicalOutput);
}
@Override
public ValueSet expand(ValueSet theSource, String theFilter) {
ValueSet toExpand = new ValueSet();
// for (UriType next : theSource.getCompose().getInclude()) {
// ConceptSetComponent include = toExpand.getCompose().addInclude();
// include.setSystem(next.getValue());
// addFilterIfPresent(theFilter, include);
// }
for (ConceptSetComponent next : theSource.getCompose().getInclude()) {
toExpand.getCompose().addInclude(next);
addFilterIfPresent(theFilter, next);
}
if (toExpand.getCompose().isEmpty()) {
throw new InvalidRequestException("ValueSet does not have any compose.include or compose.import values, can not expand");
}
toExpand.getCompose().getExclude().addAll(theSource.getCompose().getExclude());
ValueSet retVal = doExpand(toExpand);
if (isNotBlank(theFilter)) {
applyFilter(retVal.getExpansion().getTotalElement(), retVal.getExpansion().getContains(), theFilter);
}
return retVal;
org.hl7.fhir.r4.model.ValueSet canonicalInput = ValueSet40_50.convertValueSet(theSource);
org.hl7.fhir.r4.model.ValueSet canonicalOutput = myTerminologySvc.expandValueSet(null, canonicalInput, theFilter);
return ValueSet40_50.convertValueSet(canonicalOutput);
}
@Override
public ValueSet expand(ValueSet theSource, String theFilter, int theOffset, int theCount) {
ValueSet toExpand = new ValueSet();
toExpand.setId(theSource.getId());
toExpand.setUrl(theSource.getUrl());
if (theSource.getVersion() != null) {
toExpand.setVersion(theSource.getVersion());
}
for (ConceptSetComponent next : theSource.getCompose().getInclude()) {
toExpand.getCompose().addInclude(next);
addFilterIfPresent(theFilter, next);
}
if (toExpand.getCompose().isEmpty()) {
throw new InvalidRequestException("ValueSet does not have any compose.include or compose.import values, can not expand");
}
toExpand.getCompose().getExclude().addAll(theSource.getCompose().getExclude());
ValueSet retVal = doExpand(toExpand, theOffset, theCount);
if (isNotBlank(theFilter)) {
applyFilter(retVal.getExpansion().getTotalElement(), retVal.getExpansion().getContains(), theFilter);
}
return retVal;
}
private void applyFilter(IntegerType theTotalElement, List<ValueSetExpansionContainsComponent> theContains, String theFilter) {
for (int idx = 0; idx < theContains.size(); idx++) {
ValueSetExpansionContainsComponent next = theContains.get(idx);
if (isBlank(next.getDisplay()) || !org.apache.commons.lang3.StringUtils.containsIgnoreCase(next.getDisplay(), theFilter)) {
theContains.remove(idx);
idx--;
if (theTotalElement.getValue() != null) {
theTotalElement.setValue(theTotalElement.getValue() - 1);
}
}
applyFilter(theTotalElement, next.getContains(), theFilter);
}
}
private void addFilterIfPresent(String theFilter, ConceptSetComponent include) {
if (ElementUtil.isEmpty(include.getConcept())) {
if (isNotBlank(theFilter)) {
include.addFilter().setProperty("display").setOp(Enumerations.FilterOperator.EQUAL).setValue(theFilter);
}
}
ValueSetExpansionOptions options = ValueSetExpansionOptions.forOffsetAndCount(theOffset, theCount);
org.hl7.fhir.r4.model.ValueSet canonicalInput = ValueSet40_50.convertValueSet(theSource);
org.hl7.fhir.r4.model.ValueSet canonicalOutput = myTerminologySvc.expandValueSet(options, canonicalInput, theFilter);
return ValueSet40_50.convertValueSet(canonicalOutput);
}
@Override

View File

@ -154,7 +154,7 @@ public class TokenPredicateBuilder extends BaseSearchParamPredicateBuilder {
*/
if (modifier == TokenParamModifier.IN) {
codes.addAll(myTerminologySvc.expandValueSet(null, code));
codes.addAll(myTerminologySvc.expandValueSetIntoConceptList(null, code));
} else if (modifier == TokenParamModifier.ABOVE) {
system = determineSystemIfMissing(theSearchParam, code, system);
validateHaveSystemAndCodeForToken(paramName, code, system);
@ -228,7 +228,7 @@ public class TokenPredicateBuilder extends BaseSearchParamPredicateBuilder {
String valueSet = valueSetUris.iterator().next();
ValueSetExpansionOptions options = new ValueSetExpansionOptions()
.setFailOnMissingCodeSystem(false);
List<FhirVersionIndependentConcept> candidateCodes = myTerminologySvc.expandValueSet(options, valueSet);
List<FhirVersionIndependentConcept> candidateCodes = myTerminologySvc.expandValueSetIntoConceptList(options, valueSet);
for (FhirVersionIndependentConcept nextCandidate : candidateCodes) {
if (nextCandidate.getCode().equals(code)) {
retVal = nextCandidate.getSystem();

View File

@ -67,6 +67,7 @@ import ca.uhn.fhir.jpa.model.sched.HapiJob;
import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
import ca.uhn.fhir.jpa.model.sched.ScheduledJobDefinition;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.search.builder.SearchBuilder;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
@ -183,6 +184,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isEmpty;
import static org.apache.commons.lang3.StringUtils.isNoneBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.lowerCase;
public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
public static final int DEFAULT_FETCH_SIZE = 250;
@ -394,25 +396,44 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
@Override
public List<FhirVersionIndependentConcept> expandValueSet(ValueSetExpansionOptions theExpansionOptions, String theValueSet) {
ExpansionFilter expansionFilter = ExpansionFilter.NO_FILTER;
return expandValueSet(theExpansionOptions, theValueSet, expansionFilter);
}
private List<FhirVersionIndependentConcept> expandValueSet(ValueSetExpansionOptions theExpansionOptions, String theValueSet, ExpansionFilter theExpansionFilter) {
@Transactional
public List<FhirVersionIndependentConcept> expandValueSetIntoConceptList(@Nullable ValueSetExpansionOptions theExpansionOptions, @Nonnull String theValueSetCanonicalUrl) {
String expansionFilter = null;
// TODO: DM 2019-09-10 - This is problematic because an incorrect URL that matches ValueSet.id will not be found in the terminology tables but will yield a ValueSet here. Depending on the ValueSet, the expansion may time-out.
ValueSet valueSet = fetchCanonicalValueSetFromCompleteContext(theValueSet);
if (valueSet == null) {
throwInvalidValueSet(theValueSet);
ValueSet expanded = expandValueSet(theExpansionOptions, theValueSetCanonicalUrl, expansionFilter);
ArrayList<FhirVersionIndependentConcept> retVal = new ArrayList<>();
for (ValueSet.ValueSetExpansionContainsComponent nextContains : expanded.getExpansion().getContains()) {
retVal.add(new FhirVersionIndependentConcept(nextContains.getSystem(), nextContains.getCode(), nextContains.getDisplay(), nextContains.getVersion()));
}
return retVal;
}
return expandValueSetAndReturnVersionIndependentConcepts(theExpansionOptions, valueSet, theExpansionFilter);
@Override
@Transactional
public ValueSet expandValueSet(@Nullable ValueSetExpansionOptions theExpansionOptions, @Nonnull String theValueSetCanonicalUrl, @Nullable String theExpansionFilter) {
ValueSet valueSet = fetchCanonicalValueSetFromCompleteContext(theValueSetCanonicalUrl);
if (valueSet == null) {
throw new ResourceNotFoundException("Unknown ValueSet: " + UrlUtil.escapeUrlParam(theValueSetCanonicalUrl));
}
return expandValueSet(theExpansionOptions, valueSet, theExpansionFilter);
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public ValueSet expandValueSet(ValueSetExpansionOptions theExpansionOptions, ValueSet theValueSetToExpand) {
public ValueSet expandValueSet(@Nullable ValueSetExpansionOptions theExpansionOptions, @Nonnull ValueSet theValueSetToExpand) {
return expandValueSet(theExpansionOptions, theValueSetToExpand, (String) null);
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public ValueSet expandValueSet(@Nullable ValueSetExpansionOptions theExpansionOptions, @Nonnull ValueSet theValueSetToExpand, @Nullable String theFilter) {
return expandValueSet(theExpansionOptions, theValueSetToExpand, ExpansionFilter.fromFilterString(theFilter));
}
private ValueSet expandValueSet(@Nullable ValueSetExpansionOptions theExpansionOptions, ValueSet theValueSetToExpand, ExpansionFilter theFilter) {
ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSetToExpand, "ValueSet to expand can not be null");
ValueSetExpansionOptions expansionOptions = provideExpansionOptions(theExpansionOptions);
@ -431,9 +452,8 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
accumulator.addParameter().setName("count").setValue(new IntegerType(count));
}
ExpansionFilter filter = ExpansionFilter.NO_FILTER;
expandValueSetIntoAccumulator(theValueSetToExpand, theExpansionOptions, accumulator, filter, true);
expandValueSetIntoAccumulator(theValueSetToExpand, theExpansionOptions, accumulator, theFilter, true);
if (accumulator.getTotalConcepts() != null) {
accumulator.setTotal(accumulator.getTotalConcepts());
@ -449,7 +469,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
.setUrl(HapiExtensions.EXT_VALUESET_EXPANSION_MESSAGE)
.setValue(new StringType(next));
}
return valueSet;
}
@ -513,6 +532,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
boolean wasFilteredResult = false;
if (!theFilter.getFilters().isEmpty() && JpaConstants.VALUESET_FILTER_DISPLAY.equals(theFilter.getFilters().get(0).getProperty()) && theFilter.getFilters().get(0).getOp() == ValueSet.FilterOperator.EQUAL) {
String displayValue = theFilter.getFilters().get(0).getValue().replace("%", "[%]") + "%";
displayValue = lowerCase(displayValue);
conceptViews = myTermValueSetConceptViewDao.findByTermValueSetId(theTermValueSet.getId(), displayValue);
wasFilteredResult = true;
} else {
@ -702,18 +722,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return sb.toString();
}
protected List<FhirVersionIndependentConcept> expandValueSetAndReturnVersionIndependentConcepts(ValueSetExpansionOptions theExpansionOptions, ValueSet theValueSetToExpandR4, @Nonnull ExpansionFilter theExpansionFilter) {
int maxCapacity = myDaoConfig.getMaximumExpansionSize();
ValueSetExpansionComponentWithConceptAccumulator accumulator = new ValueSetExpansionComponentWithConceptAccumulator(myContext, maxCapacity);
expandValueSet(theExpansionOptions, theValueSetToExpandR4, accumulator, theExpansionFilter);
ArrayList<FhirVersionIndependentConcept> retVal = new ArrayList<>();
for (org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent nextContains : accumulator.getContains()) {
retVal.add(new FhirVersionIndependentConcept(nextContains.getSystem(), nextContains.getCode(), nextContains.getDisplay(), nextContains.getVersion()));
}
return retVal;
}
/**
* @return Returns true if there are potentially more results to process.
*/
@ -938,12 +946,12 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
* DM 2019-08-21 - Processing slows after any ValueSets with many codes explicitly identified. This might
* be due to the dark arts that is memory management. Will monitor but not do anything about this right now.
*/
BooleanQuery.setMaxClauseCount(10000);
BooleanQuery.setMaxClauseCount(SearchBuilder.getMaximumPageSize());
StopWatch sw = new StopWatch();
AtomicInteger count = new AtomicInteger(0);
int maxResultsPerBatch = 10000;
int maxResultsPerBatch = SearchBuilder.getMaximumPageSize();
/*
* If the accumulator is bounded, we may reduce the size of the query to
@ -2553,6 +2561,136 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
@Nullable
protected abstract CodeableConcept toCanonicalCodeableConcept(@Nullable IBaseDatatype theCodeableConcept);
@NotNull
private FhirVersionIndependentConcept toConcept(IPrimitiveType<String> theCodeType, IPrimitiveType<String> theCodeSystemIdentifierType, IBaseCoding theCodingType) {
String code = theCodeType != null ? theCodeType.getValueAsString() : null;
String system = theCodeSystemIdentifierType != null ? getUrlFromIdentifier(theCodeSystemIdentifierType.getValueAsString()) : null;
String systemVersion = theCodeSystemIdentifierType != null ? getVersionFromIdentifier(theCodeSystemIdentifierType.getValueAsString()) : null;
if (theCodingType != null) {
Coding canonicalizedCoding = toCanonicalCoding(theCodingType);
assert canonicalizedCoding != null; // Shouldn't be null, since theCodingType isn't
code = canonicalizedCoding.getCode();
system = canonicalizedCoding.getSystem();
systemVersion = canonicalizedCoding.getVersion();
}
return new FhirVersionIndependentConcept(system, code, null, systemVersion);
}
@Override
@Transactional
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 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, theVersion, 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, theVersion, 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));
} else {
query.orderBy(criteriaBuilder.desc(root.get("myUpdated")));
}
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(SINGLE_FETCH_SIZE);
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);
}
public static class Job implements HapiJob {
@Autowired
private ITermReadSvc myTerminologySvc;
@ -2640,21 +2778,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return termConcept;
}
@NotNull
private FhirVersionIndependentConcept toConcept(IPrimitiveType<String> theCodeType, IPrimitiveType<String> theCodeSystemIdentifierType, IBaseCoding theCodingType) {
String code = theCodeType != null ? theCodeType.getValueAsString() : null;
String system = theCodeSystemIdentifierType != null ? getUrlFromIdentifier(theCodeSystemIdentifierType.getValueAsString()): null;
String systemVersion = theCodeSystemIdentifierType != null ? getVersionFromIdentifier(theCodeSystemIdentifierType.getValueAsString()): null;
if (theCodingType != null) {
Coding canonicalizedCoding = toCanonicalCoding(theCodingType);
assert canonicalizedCoding != null; // Shouldn't be null, since theCodingType isn't
code = canonicalizedCoding.getCode();
system = canonicalizedCoding.getSystem();
systemVersion = canonicalizedCoding.getVersion();
}
return new FhirVersionIndependentConcept(system, code, null, systemVersion);
}
/**
* This method is present only for unit tests, do not call from client code
*/
@ -2686,120 +2809,4 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
static boolean isOurLastResultsFromTranslationWithReverseCache() {
return ourLastResultsFromTranslationWithReverseCache;
}
@Override
@Transactional
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 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, theVersion, 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, theVersion, 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));
} else {
query.orderBy(criteriaBuilder.desc(root.get("myUpdated")));
}
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(SINGLE_FETCH_SIZE);
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

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.term;
* #L%
*/
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.util.FhirVersionIndependentConcept;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.r4.model.ValueSet;
@ -29,6 +30,7 @@ import javax.annotation.Nullable;
import java.util.Collections;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.isNoneBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
class ExpansionFilter {
@ -95,4 +97,19 @@ class ExpansionFilter {
public Integer getMaxCount() {
return myMaxCount;
}
@Nonnull
public static ExpansionFilter fromFilterString(@Nullable String theFilter) {
ExpansionFilter filter;
if (isNoneBlank(theFilter)) {
List<ValueSet.ConceptSetFilterComponent> filters = Collections.singletonList(new ValueSet.ConceptSetFilterComponent()
.setProperty(JpaConstants.VALUESET_FILTER_DISPLAY)
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue(theFilter));
filter = new ExpansionFilter(null, null, filters, null);
} else {
filter = ExpansionFilter.NO_FILTER;
}
return filter;
}
}

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.term;
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.entity.TermConceptDesignation;
import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException;
import ca.uhn.fhir.model.api.annotation.Block;
@ -45,6 +46,15 @@ public class ValueSetExpansionComponentWithConceptAccumulator extends ValueSet.V
private int myAddedConcepts;
private Integer myTotalConcepts;
/**
* Constructor
*
* @param theDaoConfig Will be used to determine the max capacity for this accumulator
*/
public ValueSetExpansionComponentWithConceptAccumulator(FhirContext theContext, DaoConfig theDaoConfig) {
this(theContext, theDaoConfig.getMaximumExpansionSize());
}
/**
* Constructor
*

View File

@ -20,6 +20,7 @@ import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.ValueSet;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Optional;
@ -57,7 +58,11 @@ import java.util.Set;
*/
public interface ITermReadSvc extends IValidationSupport {
ValueSet expandValueSet(@Nullable ValueSetExpansionOptions theExpansionOptions, ValueSet theValueSetToExpand);
ValueSet expandValueSet(@Nullable ValueSetExpansionOptions theExpansionOptions, @Nonnull String theValueSetCanonicalUrl, @Nullable String theExpansionFilter);
ValueSet expandValueSet(@Nullable ValueSetExpansionOptions theExpansionOptions, @Nonnull ValueSet theValueSetToExpand);
ValueSet expandValueSet(@Nullable ValueSetExpansionOptions theExpansionOptions, @Nonnull ValueSet theValueSetToExpand, @Nullable String theFilter);
void expandValueSet(@Nullable ValueSetExpansionOptions theExpansionOptions, ValueSet theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator);
@ -68,7 +73,7 @@ public interface ITermReadSvc extends IValidationSupport {
void expandValueSet(@Nullable ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator);
List<FhirVersionIndependentConcept> expandValueSet(ValueSetExpansionOptions theExpansionOptions, String theValueSet);
List<FhirVersionIndependentConcept> expandValueSetIntoConceptList(ValueSetExpansionOptions theExpansionOptions, String theValueSetCanonicalUrl);
Optional<TermConcept> findCode(String theCodeSystem, String theCode);

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.jpa.search.builder.SearchBuilder;
import net.ttddyy.dsproxy.ExecutionInfo;
import net.ttddyy.dsproxy.QueryInfo;
import net.ttddyy.dsproxy.proxy.ParameterSetOperation;

View File

@ -50,6 +50,8 @@ import java.util.List;
import static ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoDstu3TerminologyTest.URL_MY_CODE_SYSTEM;
import static ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoDstu3TerminologyTest.URL_MY_VALUE_SET;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.jupiter.api.Assertions.*;
@ -343,14 +345,14 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
.operation()
.onInstance(myExtensionalVsId)
.named("expand")
.withParameter(Parameters.class, "filter", new StringType("first"))
.withParameter(Parameters.class, "filter", new StringType("systolic"))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertThat(resp, Matchers.containsString("<display value=\"Systolic blood pressure at First encounter\"/>"));
assertThat(resp, Matchers.not(Matchers.containsString("<display value=\"Systolic blood pressure--expiration\"/>")));
assertThat(resp, not(containsString("\"Foo Code\"")));
}
@ -866,7 +868,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
.onType(ValueSet.class)
.named("expand")
.withParameter(Parameters.class, "url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"))
.andParameter("filter", new StringType("first"))
.andParameter("filter", new StringType("systolic"))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();

View File

@ -385,28 +385,28 @@ public class ResourceProviderDstu3ValueSetVersionedTest extends BaseResourceProv
.operation()
.onInstance(myExtensionalVsId_v1)
.named("expand")
.withParameter(Parameters.class, "filter", new StringType("first"))
.withParameter(Parameters.class, "filter", new StringType("systolic"))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter\"/>"));
assertThat(resp, not(containsString("<display value=\"Systolic blood pressure--expiration\"/>")));
assertThat(resp, not(containsString("\"Foo Code\"")));
// Verify ValueSet v2
respParam = ourClient
.operation()
.onInstance(myExtensionalVsId_v2)
.named("expand")
.withParameter(Parameters.class, "filter", new StringType("first"))
.withParameter(Parameters.class, "filter", new StringType("systolic"))
.execute();
expanded = (ValueSet) respParam.getParameter().get(0).getResource();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter v2\"/>"));
assertThat(resp, not(containsString("<display value=\"Systolic blood pressure--expiration v2\"/>")));
assertThat(resp, not(containsString("\"Foo Code\"")));
}
@ -425,28 +425,28 @@ public class ResourceProviderDstu3ValueSetVersionedTest extends BaseResourceProv
.operation()
.onInstance(myExtensionalVsId_v1)
.named("expand")
.withParameter(Parameters.class, "filter", new StringType("first"))
.withParameter(Parameters.class, "filter", new StringType("systolic"))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter\"/>"));
assertThat(resp, not(containsString("<display value=\"Systolic blood pressure--expiration\"/>")));
assertThat(resp, not(containsString("\"Foo Code\"")));
// Validate ValueSet v2
respParam = ourClient
.operation()
.onInstance(myExtensionalVsId_v2)
.named("expand")
.withParameter(Parameters.class, "filter", new StringType("first"))
.withParameter(Parameters.class, "filter", new StringType("systolic"))
.execute();
expanded = (ValueSet) respParam.getParameter().get(0).getResource();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter v2\"/>"));
assertThat(resp, not(containsString("<display value=\"Systolic blood pressure--expiration v2\"/>")));
assertThat(resp, not(containsString("\"Foo Code\"")));
}

View File

@ -287,14 +287,14 @@ public class ResourceProviderR4ValueSetNoVerCSNoVerTest extends BaseResourceProv
.operation()
.onInstance(myExtensionalVsId)
.named("expand")
.withParameter(Parameters.class, "filter", new StringType("first"))
.withParameter(Parameters.class, "filter", new StringType("systolic"))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter\"/>"));
assertThat(resp, not(containsString("<display value=\"Systolic blood pressure--expiration\"/>")));
assertThat(resp, not(containsString("\"Foo Code\"")));
}
@ -312,14 +312,14 @@ public class ResourceProviderR4ValueSetNoVerCSNoVerTest extends BaseResourceProv
.operation()
.onInstance(myExtensionalVsId)
.named("expand")
.withParameter(Parameters.class, "filter", new StringType("first"))
.withParameter(Parameters.class, "filter", new StringType("systolic"))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter\"/>"));
assertThat(resp, not(containsString("<display value=\"Systolic blood pressure--expiration\"/>")));
assertThat(resp, not(containsString("\"Foo Code\"")));
}
@ -1203,7 +1203,7 @@ public class ResourceProviderR4ValueSetNoVerCSNoVerTest extends BaseResourceProv
.onType(ValueSet.class)
.named("expand")
.withParameter(Parameters.class, "url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"))
.andParameter("filter", new StringType("first"))
.andParameter("filter", new StringType("systolic"))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();

View File

@ -261,14 +261,14 @@ public class ResourceProviderR4ValueSetVerCSNoVerTest extends BaseResourceProvid
.operation()
.onInstance(myExtensionalVsId)
.named("expand")
.withParameter(Parameters.class, "filter", new StringType("first"))
.withParameter(Parameters.class, "filter", new StringType("systolic"))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter\"/>"));
assertThat(resp, not(containsString("<display value=\"Systolic blood pressure--expiration\"/>")));
assertThat(resp, not(containsString("\"Foo Code\"")));
}
@ -286,14 +286,14 @@ public class ResourceProviderR4ValueSetVerCSNoVerTest extends BaseResourceProvid
.operation()
.onInstance(myExtensionalVsId)
.named("expand")
.withParameter(Parameters.class, "filter", new StringType("first"))
.withParameter(Parameters.class, "filter", new StringType("systolic"))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter\"/>"));
assertThat(resp, not(containsString("<display value=\"Systolic blood pressure--expiration\"/>")));
assertThat(resp, not(containsString("\"Foo Code\"")));
}

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
@ -11,8 +12,11 @@ import ca.uhn.fhir.jpa.entity.TermValueSetConcept;
import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation;
import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.search.builder.SearchBuilder;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
@ -37,6 +41,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
@ -44,7 +49,9 @@ import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM;
import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_VALUE_SET;
@ -331,28 +338,28 @@ public class ResourceProviderR4ValueSetVerCSVerTest extends BaseResourceProvider
.operation()
.onInstance(myExtensionalVsId_v1)
.named("expand")
.withParameter(Parameters.class, "filter", new StringType("first"))
.withParameter(Parameters.class, "filter", new StringType("systolic"))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter\"/>"));
assertThat(resp, not(containsString("<display value=\"Systolic blood pressure--expiration\"/>")));
assertThat(resp, not(containsString("\"Foo Code\"")));
// Verify ValueSet v2
respParam = myClient
.operation()
.onInstance(myExtensionalVsId_v2)
.named("expand")
.withParameter(Parameters.class, "filter", new StringType("first"))
.withParameter(Parameters.class, "filter", new StringType("systolic"))
.execute();
expanded = (ValueSet) respParam.getParameter().get(0).getResource();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter v2\"/>"));
assertThat(resp, not(containsString("<display value=\"Systolic blood pressure--expiration v2\"/>")));
assertThat(resp, not(containsString("\"Foo Code\"")));
}
@ -372,28 +379,27 @@ public class ResourceProviderR4ValueSetVerCSVerTest extends BaseResourceProvider
.operation()
.onInstance(myExtensionalVsId_v1)
.named("expand")
.withParameter(Parameters.class, "filter", new StringType("first"))
.withParameter(Parameters.class, "filter", new StringType("systolic"))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter\"/>"));
assertThat(resp, not(containsString("<display value=\"Systolic blood pressure--expiration\"/>")));
// Validate ValueSet v2
respParam = myClient
.operation()
.onInstance(myExtensionalVsId_v2)
.named("expand")
.withParameter(Parameters.class, "filter", new StringType("first"))
.withParameter(Parameters.class, "filter", new StringType("systolic"))
.execute();
expanded = (ValueSet) respParam.getParameter().get(0).getResource();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter v2\"/>"));
assertThat(resp, not(containsString("<display value=\"Systolic blood pressure--expiration v2\"/>")));
assertThat(resp, not(containsString("\"Foo Code\"")));
}

View File

@ -70,7 +70,6 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
private IIdType myExtensionalCsId;
private IIdType myExtensionalVsId;
private IIdType myLocalValueSetId;
private Long myExtensionalCsIdOnResourceTable;
private Long myExtensionalVsIdOnResourceTable;
private ValueSet myLocalVs;
@ -84,11 +83,6 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
loadAndPersistValueSet(theVerb);
}
private void loadAndPersistCodeSystemAndValueSetWithDesignationsAndExclude(HTTPVerb theVerb) throws IOException {
loadAndPersistCodeSystemWithDesignations(theVerb);
loadAndPersistValueSetWithExclude(theVerb);
}
private void loadAndPersistCodeSystem(HTTPVerb theVerb) throws IOException {
CodeSystem codeSystem = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs.xml");
codeSystem.setId("CodeSystem/cs");
@ -123,7 +117,6 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
default:
throw new IllegalArgumentException("HTTP verb is not supported: " + theVerb);
}
myExtensionalCsIdOnResourceTable = myCodeSystemDao.readEntity(myExtensionalCsId, null).getId();
}
private void loadAndPersistValueSet(HTTPVerb theVerb) throws IOException {
@ -132,12 +125,6 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
persistValueSet(valueSet, theVerb);
}
private void loadAndPersistValueSetWithExclude(HTTPVerb theVerb) throws IOException {
ValueSet valueSet = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs-with-exclude.xml");
valueSet.setId("ValueSet/vs");
persistValueSet(valueSet, theVerb);
}
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
private void persistValueSet(ValueSet theValueSet, HTTPVerb theVerb) {
switch (theVerb) {
@ -404,14 +391,14 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
.operation()
.onInstance(myExtensionalVsId)
.named("expand")
.withParameter(Parameters.class, "filter", new StringType("first"))
.withParameter(Parameters.class, "filter", new StringType("systolic"))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter\"/>"));
assertThat(resp, not(containsString("<display value=\"Systolic blood pressure--expiration\"/>")));
assertThat(resp, not(containsString("\"Foo Code\"")));
}
@ -426,14 +413,14 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
.operation()
.onInstance(myExtensionalVsId)
.named("expand")
.withParameter(Parameters.class, "filter", new StringType("first"))
.withParameter(Parameters.class, "filter", new StringType("systolic"))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter\"/>"));
assertThat(resp, not(containsString("<display value=\"Systolic blood pressure--expiration\"/>")));
assertThat(resp, not(containsString("\"Foo Code\"")));
}
@ -1449,7 +1436,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
.onType(ValueSet.class)
.named("expand")
.withParameter(Parameters.class, "url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"))
.andParameter("filter", new StringType("first"))
.andParameter("filter", new StringType("systolic"))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();

View File

@ -368,28 +368,28 @@ public class ResourceProviderR5ValueSetVersionedTest extends BaseResourceProvide
.operation()
.onInstance(myExtensionalVsId_v1)
.named("expand")
.withParameter(Parameters.class, "filter", new StringType("first"))
.withParameter(Parameters.class, "filter", new StringType("systolic"))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter\"/>"));
assertThat(resp, not(containsString("<display value=\"Systolic blood pressure--expiration\"/>")));
assertThat(resp, not(containsString("\"Foo Code\"")));
// Verify ValueSet v2
respParam = myClient
.operation()
.onInstance(myExtensionalVsId_v2)
.named("expand")
.withParameter(Parameters.class, "filter", new StringType("first"))
.withParameter(Parameters.class, "filter", new StringType("systolic"))
.execute();
expanded = (ValueSet) respParam.getParameter().get(0).getResource();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter v2\"/>"));
assertThat(resp, not(containsString("<display value=\"Systolic blood pressure--expiration v2\"/>")));
assertThat(resp, not(containsString("\"Foo Code\"")));
}
@ -409,28 +409,28 @@ public class ResourceProviderR5ValueSetVersionedTest extends BaseResourceProvide
.operation()
.onInstance(myExtensionalVsId_v1)
.named("expand")
.withParameter(Parameters.class, "filter", new StringType("first"))
.withParameter(Parameters.class, "filter", new StringType("systolic"))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter\"/>"));
assertThat(resp, not(containsString("<display value=\"Systolic blood pressure--expiration\"/>")));
assertThat(resp, not(containsString("\"Foo Code\"")));
// Validate ValueSet v2
respParam = myClient
.operation()
.onInstance(myExtensionalVsId_v2)
.named("expand")
.withParameter(Parameters.class, "filter", new StringType("first"))
.withParameter(Parameters.class, "filter", new StringType("systolic"))
.execute();
expanded = (ValueSet) respParam.getParameter().get(0).getResource();
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter v2\"/>"));
assertThat(resp, not(containsString("<display value=\"Systolic blood pressure--expiration v2\"/>")));
assertThat(resp, not(containsString("\"Foo Code\"")));
}

View File

@ -11,6 +11,7 @@ import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation;
import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.search.builder.SearchBuilder;
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
import ca.uhn.fhir.jpa.util.SqlQuery;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -22,10 +23,12 @@ import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r4.model.codesystems.HttpVerb;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Pageable;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
@ -60,6 +63,11 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
@Mock
private IValueSetConceptAccumulator myValueSetCodeAccumulator;
@AfterEach
public void afterEach() {
SearchBuilder.setMaxPageSize50ForTest(false);
}
@Test
public void testDeletePreExpandedValueSet() throws IOException {
myDaoConfig.setPreExpandValueSets(true);
@ -181,7 +189,53 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
// Make sure we used the pre-expanded version
List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueries();
String lastSelectQuery = selectQueries.get(selectQueries.size() - 1).getSql(true, true).toLowerCase();
assertThat(lastSelectQuery, containsString("concept_display like 'display value 9%'"));
assertThat(lastSelectQuery, containsString(" like 'display value 9%'"));
}
@Test
public void testExpandHugeValueSet_FilterOnDisplay_LeftMatch_SelectAll() {
SearchBuilder.setMaxPageSize50ForTest(true);
myDaoConfig.setPreExpandValueSets(true);
IIdType vsId = createConceptsCodeSystemAndValueSet(1005);
// Inline ValueSet
{
ValueSet input = new ValueSet();
input.getCompose()
.addInclude()
.addValueSet("http://foo/vs")
.addFilter()
.setProperty(JpaConstants.VALUESET_FILTER_DISPLAY)
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("display value 100");
// Expansion should contain all codes
myCaptureQueriesListener.clear();
ValueSet expandedValueSet = myTermSvc.expandValueSet(new ValueSetExpansionOptions(), input);
List<String> codes = expandedValueSet.getExpansion().getContains().stream().map(t -> t.getCode()).collect(Collectors.toList());
assertThat(codes.toString(), codes, contains("code100", "code1000", "code1001", "code1002", "code1003", "code1004"));
// Make sure we used the pre-expanded version
List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueries();
String lastSelectQuery = selectQueries.get(selectQueries.size() - 1).getSql(true, true).toLowerCase();
ourLog.info("SQL: {}", lastSelectQuery);
assertThat(lastSelectQuery, containsString(" like 'display value 100%'"));
}
// ValueSet by ID
{
myCaptureQueriesListener.clear();
ValueSet expandedValueSet = myValueSetDao.expand(vsId, "display value 100", 0, 1000, mySrd);
List<String> codes = expandedValueSet.getExpansion().getContains().stream().map(t -> t.getCode()).collect(Collectors.toList());
assertThat(codes.toString(), codes, contains("code100", "code1000", "code1001", "code1002", "code1003", "code1004"));
// Make sure we used the pre-expanded version
List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueries();
String lastSelectQuery = selectQueries.get(selectQueries.size() - 1).getSql(true, true).toLowerCase();
ourLog.info("SQL: {}", lastSelectQuery);
assertThat(lastSelectQuery, containsString(" like 'display value 100%'"));
}
}
@ -215,11 +269,38 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
// Make sure we used the pre-expanded version
List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueries();
String lastSelectQuery = selectQueries.get(selectQueries.size() - 1).getSql(true, true).toLowerCase();
assertThat(lastSelectQuery, containsString("concept_display like 'display value 9%'"));
assertThat(lastSelectQuery, containsString(" like 'display value 9%'"));
}
@Test
public void testExpandInline_IncludePreExpandedValueSetByUri_FilterOnDisplay_LeftMatchCaseInsensitive() {
myDaoConfig.setPreExpandValueSets(true);
create100ConceptsCodeSystemAndValueSet();
ValueSet input = new ValueSet();
input.getCompose()
.addInclude()
.addValueSet("http://foo/vs")
.addFilter()
.setProperty(JpaConstants.VALUESET_FILTER_DISPLAY)
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("dIsPlAy valuE 99");
myCaptureQueriesListener.clear();
ValueSet expandedValueSet = myTermSvc.expandValueSet(null, input);
ourLog.debug("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet));
assertThat(toCodes(expandedValueSet).toString(), toCodes(expandedValueSet), contains( "code99" ));
// Make sure we used the pre-expanded version
List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueries();
String lastSelectQuery = selectQueries.get(selectQueries.size() - 1).getSql(true, true).toLowerCase();
assertThat(lastSelectQuery, containsString("like 'display value 99%'"));
}
@Test
public void testExpandInline_IncludePreExpandedValueSetByUri_ExcludeCodes_FilterOnDisplay_LeftMatch_SelectAll() {
myDaoConfig.setPreExpandValueSets(true);
@ -254,7 +335,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
// Make sure we used the pre-expanded version
List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueries();
String lastSelectQuery = selectQueries.get(selectQueries.size() - 1).getSql(true, true).toLowerCase();
assertThat(lastSelectQuery, containsString("concept_display like 'display value 90%'"));
assertThat(lastSelectQuery, containsString(" like 'display value 90%'"));
}
@ -292,24 +373,38 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
}
}
public void create100ConceptsCodeSystemAndValueSet() {
createConceptsCodeSystemAndValueSet(100);
}
public IIdType createConceptsCodeSystemAndValueSet(int theCount) {
CodeSystem cs = new CodeSystem();
cs.setUrl("http://foo/cs");
cs.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT);
myCodeSystemDao.create(cs);
CustomTerminologySet additions = new CustomTerminologySet();
for (int i = 0; i < 100; i++) {
for (int i = 0; i < theCount; i++) {
additions.addRootConcept("code" + i, "display value " + i);
}
myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo/cs", additions);
myTerminologyDeferredStorageSvc.saveAllDeferred();
ValueSet vs = new ValueSet();
vs.setUrl("http://foo/vs");
vs.getCompose().addInclude().setSystem("http://foo/cs");
myValueSetDao.create(vs);
IIdType vsId = myValueSetDao.create(vs).getId().toUnqualifiedVersionless();
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
// Confirm we pre-expanded successfully
runInTransaction(() -> {
Pageable page = Pageable.unpaged();
List<TermValueSet> valueSets = myTermValueSetDao.findTermValueSetByUrl(page, "http://foo/vs");
assertEquals(1, valueSets.size());
assertEquals(TermValueSetPreExpansionStatusEnum.EXPANDED, valueSets.get(0).getExpansionStatus());
});
return vsId;
}
@Test
@ -339,7 +434,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
// Make sure we used the pre-expanded version
List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueries();
String lastSelectQuery = selectQueries.get(selectQueries.size() - 1).getSql(true, true).toLowerCase();
assertThat(lastSelectQuery, containsString("concept_display like 'display value 9%'"));
assertThat(lastSelectQuery, containsString(" like 'display value 9%'"));
}
@Nonnull

View File

@ -89,12 +89,12 @@
<code value="8490-5" />
<display value="Systolic blood pressure 24 hour mean" />
</concept>
<concept>
<code value="8491-3" />
<display value="Systolic blood pressure 1 hour minimum" />
</concept>
<concept>
<code value="8492-1" />
<display value="Systolic blood pressure 8 hour minimum" />
</concept>
<concept>
<code value="9999-9" />
<display value="Foo Code" />
</concept>
</CodeSystem>