From fb8658da70ba48abe2b0b64ffa91ef9601a4654c Mon Sep 17 00:00:00 2001 From: James Agnew Date: Tue, 24 Nov 2020 18:12:02 -0500 Subject: [PATCH] Always apply vs filters correctly (#2195) * Always apply valueset filters correctly * Test fix * Testfix * Test fix * Add changelog * Test fix --- .../support/ValueSetExpansionOptions.java | 6 + .../2195-handle-vs-filters-on-large-vs.yaml | 6 + .../dao/data/ITermValueSetConceptViewDao.java | 2 +- .../dstu3/FhirResourceDaoValueSetDstu3.java | 201 ++-------- .../dao/predicate/PredicateBuilderToken.java | 4 +- .../jpa/dao/r4/FhirResourceDaoValueSetR4.java | 164 +-------- .../jpa/dao/r5/FhirResourceDaoValueSetR5.java | 172 +-------- .../predicate/TokenPredicateBuilder.java | 4 +- .../fhir/jpa/term/BaseTermReadSvcImpl.java | 345 +++++++++--------- .../ca/uhn/fhir/jpa/term/ExpansionFilter.java | 17 + ...ansionComponentWithConceptAccumulator.java | 12 +- .../uhn/fhir/jpa/term/api/ITermReadSvc.java | 9 +- .../BlockLargeNumbersOfParamsListener.java | 1 + .../ResourceProviderDstu3ValueSetTest.java | 8 +- ...rceProviderDstu3ValueSetVersionedTest.java | 16 +- ...rceProviderR4ValueSetNoVerCSNoVerTest.java | 10 +- ...ourceProviderR4ValueSetVerCSNoVerTest.java | 8 +- ...esourceProviderR4ValueSetVerCSVerTest.java | 22 +- .../r5/ResourceProviderR5ValueSetTest.java | 23 +- ...sourceProviderR5ValueSetVersionedTest.java | 16 +- .../jpa/term/ValueSetExpansionR4Test.java | 109 +++++- .../test/resources/extensional-case-3-cs.xml | 8 +- 22 files changed, 427 insertions(+), 736 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_3_0/2195-handle-vs-filters-on-large-vs.yaml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/ValueSetExpansionOptions.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/ValueSetExpansionOptions.java index c7667cc1fad..66bd34f24d4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/ValueSetExpansionOptions.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/ValueSetExpansionOptions.java @@ -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); + } } diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_3_0/2195-handle-vs-filters-on-large-vs.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_3_0/2195-handle-vs-filters-on-large-vs.yaml new file mode 100644 index 00000000000..5e8863e858d --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_3_0/2195-handle-vs-filters-on-large-vs.yaml @@ -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." diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetConceptViewDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetConceptViewDao.java index ae3889d0a79..bde98e11401 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetConceptViewDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermValueSetConceptViewDao.java @@ -34,7 +34,7 @@ public interface ITermValueSetConceptViewDao extends JpaRepository= :from AND v.myConceptOrder < :to ORDER BY v.myConceptOrder") List 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 findByTermValueSetId(@Param("pid") Long theValueSetId, @Param("display") String theDisplay); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java index 057caa1d8e5..88d68c17804 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoValueSetDstu3.java @@ -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 implements IFhirResourceDaoValueSet { - 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 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 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 diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java index 3cb7f7a671a..718bee5349d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java @@ -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 candidateCodes = myTerminologySvc.expandValueSet(options, valueSet); + List candidateCodes = myTerminologySvc.expandValueSetIntoConceptList(options, valueSet); for (FhirVersionIndependentConcept nextCandidate : candidateCodes) { if (nextCandidate.getCode().equals(code)) { retVal = nextCandidate.getSystem(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java index 554f9dfed8d..c1fac470c31 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java @@ -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 implements IFhirResourceDaoValueSet { - 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 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 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 diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoValueSetR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoValueSetR5.java index 44a7af52312..88c520e4bb5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoValueSetR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoValueSetR5.java @@ -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 implements IFhirResourceDaoValueSet { - 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 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 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 diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/predicate/TokenPredicateBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/predicate/TokenPredicateBuilder.java index 3616a2c1c07..79cffedc1be 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/predicate/TokenPredicateBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/predicate/TokenPredicateBuilder.java @@ -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 candidateCodes = myTerminologySvc.expandValueSet(options, valueSet); + List candidateCodes = myTerminologySvc.expandValueSetIntoConceptList(options, valueSet); for (FhirVersionIndependentConcept nextCandidate : candidateCodes) { if (nextCandidate.getCode().equals(code)) { retVal = nextCandidate.getSystem(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java index dbe467a4ef3..fc5a7a608c5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java @@ -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 expandValueSet(ValueSetExpansionOptions theExpansionOptions, String theValueSet) { - ExpansionFilter expansionFilter = ExpansionFilter.NO_FILTER; - return expandValueSet(theExpansionOptions, theValueSet, expansionFilter); - } - - private List expandValueSet(ValueSetExpansionOptions theExpansionOptions, String theValueSet, ExpansionFilter theExpansionFilter) { + @Transactional + public List 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); + ValueSet expanded = expandValueSet(theExpansionOptions, theValueSetCanonicalUrl, expansionFilter); + + ArrayList retVal = new ArrayList<>(); + for (ValueSet.ValueSetExpansionContainsComponent nextContains : expanded.getExpansion().getContains()) { + retVal.add(new FhirVersionIndependentConcept(nextContains.getSystem(), nextContains.getCode(), nextContains.getDisplay(), nextContains.getVersion())); + } + return retVal; + } + + @Override + @Transactional + public ValueSet expandValueSet(@Nullable ValueSetExpansionOptions theExpansionOptions, @Nonnull String theValueSetCanonicalUrl, @Nullable String theExpansionFilter) { + ValueSet valueSet = fetchCanonicalValueSetFromCompleteContext(theValueSetCanonicalUrl); if (valueSet == null) { - throwInvalidValueSet(theValueSet); + throw new ResourceNotFoundException("Unknown ValueSet: " + UrlUtil.escapeUrlParam(theValueSetCanonicalUrl)); } - return expandValueSetAndReturnVersionIndependentConcepts(theExpansionOptions, valueSet, theExpansionFilter); + 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 { @@ -675,7 +695,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { if (TransactionSynchronizationManager.isSynchronizationActive()) { return theAction.get(); } - return myTxTemplate.execute(t->theAction.get()); + return myTxTemplate.execute(t -> theAction.get()); } private String getValueSetInfo(ValueSet theValueSet) { @@ -702,18 +722,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { return sb.toString(); } - protected List expandValueSetAndReturnVersionIndependentConcepts(ValueSetExpansionOptions theExpansionOptions, ValueSet theValueSetToExpandR4, @Nonnull ExpansionFilter theExpansionFilter) { - int maxCapacity = myDaoConfig.getMaximumExpansionSize(); - ValueSetExpansionComponentWithConceptAccumulator accumulator = new ValueSetExpansionComponentWithConceptAccumulator(myContext, maxCapacity); - expandValueSet(theExpansionOptions, theValueSetToExpandR4, accumulator, theExpansionFilter); - - ArrayList 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 @@ -1395,7 +1403,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } } - return createFailureCodeValidationResult(theSystem, theCode, " - Concept Display \"" + theDisplay + "\" does not match expected \"" + concepts.get(0).getDisplay() + "\"").setDisplay(concepts.get(0).getDisplay()); + return createFailureCodeValidationResult(theSystem, theCode, " - Concept Display \"" + theDisplay + "\" does not match expected \"" + concepts.get(0).getDisplay() + "\"").setDisplay(concepts.get(0).getDisplay()); } if (!concepts.isEmpty()) { @@ -1424,7 +1432,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { int versionIndex = theSystem.indexOf("|"); if (versionIndex >= 0) { String systemUrl = theSystem.substring(0, versionIndex); - String systemVersion = theSystem.substring(versionIndex+1); + String systemVersion = theSystem.substring(versionIndex + 1); optionalTermValueSetConcept = myValueSetConceptDao.findByValueSetResourcePidSystemAndCodeWithVersion(theResourcePid.getIdAsLong(), systemUrl, systemVersion, theCode); } else { optionalTermValueSetConcept = myValueSetConceptDao.findByValueSetResourcePidSystemAndCode(theResourcePid.getIdAsLong(), theSystem, theCode); @@ -1526,7 +1534,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { private String getUrlFromIdentifier(String theUri) { String retVal = theUri; - if (StringUtils.isNotEmpty((theUri))){ + if (StringUtils.isNotEmpty((theUri))) { int versionSeparator = theUri.lastIndexOf('|'); if (versionSeparator != -1) { retVal = theUri.substring(0, versionSeparator); @@ -1781,9 +1789,9 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } else { String msg = myContext.getLocalizer().getMessage( BaseTermReadSvcImpl.class, - "cannotCreateDuplicateConceptMapUrlAndVersion", - conceptMapUrl, conceptMapVersion, - existingTermConceptMap.getResource().getIdDt().toUnqualifiedVersionless().getValue()); + "cannotCreateDuplicateConceptMapUrlAndVersion", + conceptMapUrl, conceptMapVersion, + existingTermConceptMap.getResource().getIdDt().toUnqualifiedVersionless().getValue()); throw new UnprocessableEntityException(msg); } } @@ -1818,7 +1826,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { // We have a ValueSet to pre-expand. try { ValueSet valueSet = txTemplate.execute(t -> { - TermValueSet refreshedValueSetToExpand = myValueSetDao.findById(valueSetToExpand.getId()).orElseThrow(()->new IllegalStateException("Unknown VS ID: " + valueSetToExpand.getId())); + TermValueSet refreshedValueSetToExpand = myValueSetDao.findById(valueSetToExpand.getId()).orElseThrow(() -> new IllegalStateException("Unknown VS ID: " + valueSetToExpand.getId())); return getValueSetFromResourceTable(refreshedValueSetToExpand.getResource()); }); assert valueSet != null; @@ -1988,7 +1996,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { @Override @Transactional public IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, - IPrimitiveType theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB) { + IPrimitiveType theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB) { FhirVersionIndependentConcept conceptA = toConcept(theCodeA, theSystem, theCodingA); FhirVersionIndependentConcept conceptB = toConcept(theCodeB, theSystem, theCodingB); @@ -2002,7 +2010,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { String codeASystemIdentifier; if (StringUtils.isNotEmpty(conceptA.getSystemVersion())) { - codeASystemIdentifier = conceptA.getSystem() + "|" + conceptA.getSystemVersion(); + codeASystemIdentifier = conceptA.getSystem() + "|" + conceptA.getSystemVersion(); } else { codeASystemIdentifier = conceptA.getSystem(); } @@ -2342,7 +2350,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { Pageable page = PageRequest.of(0, 1); List theConceptMapList = myConceptMapDao.getTermConceptMapEntitiesByUrlOrderByMostRecentUpdate(page, - theTranslationRequest.getUrl().asStringValue()); + theTranslationRequest.getUrl().asStringValue()); if (!theConceptMapList.isEmpty()) { return theConceptMapList.get(0).getVersion(); } @@ -2553,6 +2561,136 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { @Nullable protected abstract CodeableConcept toCanonicalCodeableConcept(@Nullable IBaseDatatype theCodeableConcept); + @NotNull + private FhirVersionIndependentConcept toConcept(IPrimitiveType theCodeType, IPrimitiveType 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 query = criteriaBuilder.createQuery(TermConcept.class); + Root root = query.from(TermConcept.class); + + Fetch systemVersionFetch = root.fetch("myCodeSystem", JoinType.INNER); + Join systemVersionJoin = (Join) systemVersionFetch; + Fetch systemFetch = systemVersionFetch.fetch("myCodeSystem", JoinType.INNER); + Join systemJoin = (Join) systemFetch; + + ArrayList 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 typedQuery = myEntityManager.createQuery(query.select(root)); + org.hibernate.query.Query hibernateQuery = (org.hibernate.query.Query) typedQuery; + hibernateQuery.setFetchSize(SINGLE_FETCH_SIZE); + List 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 theCodeType, IPrimitiveType 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 query = criteriaBuilder.createQuery(TermConcept.class); - Root root = query.from(TermConcept.class); - - Fetch systemVersionFetch = root.fetch("myCodeSystem", JoinType.INNER); - Join systemVersionJoin = (Join)systemVersionFetch; - Fetch systemFetch = systemVersionFetch.fetch("myCodeSystem", JoinType.INNER); - Join systemJoin = (Join)systemFetch; - - ArrayList 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 typedQuery = myEntityManager.createQuery(query.select(root)); - org.hibernate.query.Query hibernateQuery = (org.hibernate.query.Query) typedQuery; - hibernateQuery.setFetchSize(SINGLE_FETCH_SIZE); - List 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); - } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ExpansionFilter.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ExpansionFilter.java index ee5a067aa4c..ebbd6e796b0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ExpansionFilter.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ExpansionFilter.java @@ -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 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; + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ValueSetExpansionComponentWithConceptAccumulator.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ValueSetExpansionComponentWithConceptAccumulator.java index e401a108b0f..277a4185660 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ValueSetExpansionComponentWithConceptAccumulator.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/ValueSetExpansionComponentWithConceptAccumulator.java @@ -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 * @@ -56,7 +66,7 @@ public class ValueSetExpansionComponentWithConceptAccumulator extends ValueSet.V myContext = theContext; } - @Nonnull + @Nonnull @Override public Integer getCapacityRemaining() { return (myMaxCapacity - myAddedConcepts) + mySkipCountRemaining; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java index 465e7f562b5..48e972fccb3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java @@ -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 expandValueSet(ValueSetExpansionOptions theExpansionOptions, String theValueSet); + List expandValueSetIntoConceptList(ValueSetExpansionOptions theExpansionOptions, String theValueSetCanonicalUrl); Optional findCode(String theCodeSystem, String theCode); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/BlockLargeNumbersOfParamsListener.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/BlockLargeNumbersOfParamsListener.java index 57d76038c5a..25bea78ce75 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/BlockLargeNumbersOfParamsListener.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/BlockLargeNumbersOfParamsListener.java @@ -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; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java index 3d1daac2ae2..1223c654d48 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java @@ -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("")); - assertThat(resp, Matchers.not(Matchers.containsString(""))); + 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(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetVersionedTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetVersionedTest.java index 68e3adc2b49..78b47bec79e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetVersionedTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetVersionedTest.java @@ -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("")); - assertThat(resp, not(containsString(""))); + 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("")); - assertThat(resp, not(containsString(""))); + 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("")); - assertThat(resp, not(containsString(""))); + 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("")); - assertThat(resp, not(containsString(""))); + assertThat(resp, not(containsString("\"Foo Code\""))); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetNoVerCSNoVerTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetNoVerCSNoVerTest.java index b510c1d899a..9a623c14c27 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetNoVerCSNoVerTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetNoVerCSNoVerTest.java @@ -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("")); - assertThat(resp, not(containsString(""))); + 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("")); - assertThat(resp, not(containsString(""))); + 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(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetVerCSNoVerTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetVerCSNoVerTest.java index 9fd4ab74419..de4bbcbc275 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetVerCSNoVerTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetVerCSNoVerTest.java @@ -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("")); - assertThat(resp, not(containsString(""))); + 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("")); - assertThat(resp, not(containsString(""))); + assertThat(resp, not(containsString("\"Foo Code\""))); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetVerCSVerTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetVerCSVerTest.java index 35e8d253de8..0d58fe9f55f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetVerCSVerTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetVerCSVerTest.java @@ -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("")); - assertThat(resp, not(containsString(""))); + 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("")); - assertThat(resp, not(containsString(""))); + 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("")); - assertThat(resp, not(containsString(""))); // 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("")); - assertThat(resp, not(containsString(""))); + assertThat(resp, not(containsString("\"Foo Code\""))); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java index 9cdf0cbab6c..0ead9290686 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java @@ -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("")); - assertThat(resp, not(containsString(""))); + 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("")); - assertThat(resp, not(containsString(""))); + 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(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetVersionedTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetVersionedTest.java index 271425f9e0f..36ed4a694bf 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetVersionedTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetVersionedTest.java @@ -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("")); - assertThat(resp, not(containsString(""))); + 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("")); - assertThat(resp, not(containsString(""))); + 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("")); - assertThat(resp, not(containsString(""))); + 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("")); - assertThat(resp, not(containsString(""))); + assertThat(resp, not(containsString("\"Foo Code\""))); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ValueSetExpansionR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ValueSetExpansionR4Test.java index d24cb78e467..1efe466eedd 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ValueSetExpansionR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ValueSetExpansionR4Test.java @@ -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 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 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 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 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 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 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 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 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 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 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 diff --git a/hapi-fhir-jpaserver-base/src/test/resources/extensional-case-3-cs.xml b/hapi-fhir-jpaserver-base/src/test/resources/extensional-case-3-cs.xml index 0cb1edad4a4..752f65a1fc3 100644 --- a/hapi-fhir-jpaserver-base/src/test/resources/extensional-case-3-cs.xml +++ b/hapi-fhir-jpaserver-base/src/test/resources/extensional-case-3-cs.xml @@ -89,12 +89,12 @@ - - - - + + + +