diff --git a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/engine/terminology/RepositoryTerminologyProvider.java b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/engine/terminology/RepositoryTerminologyProvider.java index 226247bbd6..16a3b873d2 100644 --- a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/engine/terminology/RepositoryTerminologyProvider.java +++ b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/engine/terminology/RepositoryTerminologyProvider.java @@ -34,6 +34,7 @@ * search, but found that the performance was reduced by ~33%. Please run the benchmarks to verify * that changes to this class do not result in significant performance degradation. */ +@SuppressWarnings("UnstableApiUsage") public class RepositoryTerminologyProvider implements TerminologyProvider { private static final Logger logger = LoggerFactory.getLogger(RepositoryTerminologyProvider.class); diff --git a/cqf-fhir-cql/src/test/java/org/opencds/cqf/fhir/cql/engine/terminology/RepositoryTerminologyProviderTest.java b/cqf-fhir-cql/src/test/java/org/opencds/cqf/fhir/cql/engine/terminology/RepositoryTerminologyProviderTest.java index a038586d11..b3a03dd090 100644 --- a/cqf-fhir-cql/src/test/java/org/opencds/cqf/fhir/cql/engine/terminology/RepositoryTerminologyProviderTest.java +++ b/cqf-fhir-cql/src/test/java/org/opencds/cqf/fhir/cql/engine/terminology/RepositoryTerminologyProviderTest.java @@ -9,7 +9,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.repository.IRepository; -import java.util.Map; +import com.google.common.collect.Multimap; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.ValueSet; import org.junit.jupiter.api.Test; @@ -17,6 +17,7 @@ import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; import org.opencds.cqf.cql.engine.terminology.ValueSetInfo; +@SuppressWarnings("UnstableApiUsage") class RepositoryTerminologyProviderTest { private static final String SYSTEM_FOR_CODES = "http://example.com/CodeSystem/Codes"; @@ -77,7 +78,7 @@ IRepository mockRepositoryWithValueSet(ValueSet valueSet) { when(mockRepository.fhirContext()).thenReturn(FhirContext.forR4Cached()); Bundle bundle = new Bundle(); bundle.addEntry().setFullUrl(valueSet.getUrl()).setResource(valueSet); - when(mockRepository.search(any(), any(), any(Map.class), isNull())).thenReturn(bundle); + when(mockRepository.search(any(), any(), any(Multimap.class), isNull())).thenReturn(bundle); return mockRepository; } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/common/ResourceResolver.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/common/ResourceResolver.java index 74d0d54e89..e7872c3014 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/common/ResourceResolver.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/common/ResourceResolver.java @@ -4,6 +4,7 @@ import static org.opencds.cqf.fhir.utility.Resources.castOrThrow; import ca.uhn.fhir.repository.IRepository; +import ca.uhn.fhir.util.bundle.BundleEntryParts; import java.util.function.Function; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IBaseBundle; @@ -15,6 +16,7 @@ import org.opencds.cqf.fhir.utility.monad.Either3; import org.opencds.cqf.fhir.utility.search.Searches; +@SuppressWarnings("UnstableApiUsage") public class ResourceResolver { final String invalidResourceType = "The resource passed in was not a valid instance of %s.class"; final String resourceType; @@ -50,7 +52,7 @@ public ResourceResolver(String resourceType, IRepository repository) { protected > IBaseResource resolveByUrl(C url) { var result = this.repository.search(bundleClazz, clazz, Searches.byCanonical(url.getValue())); - var iterator = new BundleMappingIterable<>(repository, result, p -> p.getResource()).iterator(); + var iterator = new BundleMappingIterable<>(repository, result, BundleEntryParts::getResource).iterator(); return iterator.hasNext() ? iterator.next() : null; } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/graphdefinition/apply/ApplyProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/graphdefinition/apply/ApplyProcessor.java index f61882d558..cfba14e5c3 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/graphdefinition/apply/ApplyProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/graphdefinition/apply/ApplyProcessor.java @@ -9,6 +9,7 @@ import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import com.google.common.collect.Multimap; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -143,7 +144,7 @@ protected SectionComponent transformTargetToSection( return sectionComponent; } - protected Map> getSearchParams( + protected Multimap> getSearchParams( ApplyRequest request, String type, String profile) { var searchParams = Searches.byProfile(profile); searchParams.put( diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/common/InputParametersTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/common/InputParametersTest.java index bc7c2e0531..4c354fcf65 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/common/InputParametersTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/common/InputParametersTest.java @@ -13,7 +13,6 @@ import com.google.common.collect.Multimap; import java.util.Arrays; import java.util.List; -import java.util.Map; import org.hl7.fhir.instance.model.api.IBaseExtension; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; @@ -570,7 +569,7 @@ void testResolveInputParametersWithDataRequirements() { var obsBundle = new Bundle().addEntry(new BundleEntryComponent().setResource(obs)); doReturn(fhirContextR4).when(repository).fhirContext(); doReturn(patient).when(repository).read(org.hl7.fhir.r4.model.Patient.class, patient.getIdElement()); - doReturn(valueSetBundle).when(repository).search(eq(Bundle.class), eq(ValueSet.class), any(Map.class)); + doReturn(valueSetBundle).when(repository).search(eq(Bundle.class), eq(ValueSet.class), any(Multimap.class)); doReturn(obsBundle) .when(repository) .search(eq(Bundle.class), eq(Observation.class), any(Multimap.class), any()); diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/library/LibraryProcessorTests.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/library/LibraryProcessorTests.java index 1ea41ba6c1..7041686b4b 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/library/LibraryProcessorTests.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/library/LibraryProcessorTests.java @@ -12,6 +12,7 @@ import java.nio.file.Path; import java.util.List; import org.hl7.fhir.r4.model.Library; +import org.hl7.fhir.r4.model.StringType; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -201,4 +202,31 @@ void testPrefetchData() { .thenEvaluate() .hasResults(6); } + + @Test + void testEvaluateMultipleLibraryVersions() { + var libraryUrl = "http://fhir.org/guides/cdc/opioid-cds/Library/HelloWorld"; + var version1 = "1.0.0"; + var version2 = "2.0.0"; + var repository = + new IgRepository(fhirContextR4, Path.of(getResourcePath(this.getClass()) + "/" + CLASS_PATH + "/r4")); + var evaluationSettings = EvaluationSettings.getDefault(); + given().repository(repository) + .evaluationSettings(evaluationSettings) + .when() + .libraryUrl(String.format("%s|%s", libraryUrl, version1)) + .subjectId("Patient1") + .thenEvaluate() + .hasResults(8) + .resultHasValue(3, new StringType("Hello World!")); + + given().repository(repository) + .evaluationSettings(evaluationSettings) + .when() + .libraryUrl(String.format("%s|%s", libraryUrl, version2)) + .subjectId("Patient1") + .thenEvaluate() + .hasResults(8) + .resultHasValue(3, new StringType("Hello World! I am a new version!")); + } } diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/library/TestLibrary.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/library/TestLibrary.java index a15120ee3f..5cf3f7c74d 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/library/TestLibrary.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/library/TestLibrary.java @@ -1,6 +1,7 @@ package org.opencds.cqf.fhir.cr.library; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opencds.cqf.fhir.test.Resources.getResourcePath; import static org.opencds.cqf.fhir.utility.BundleHelper.addEntry; @@ -28,6 +29,7 @@ import org.hl7.fhir.instance.model.api.IBaseParameters; 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.opencds.cqf.cql.engine.model.ModelResolver; import org.opencds.cqf.fhir.cql.EvaluationSettings; import org.opencds.cqf.fhir.cql.engine.retrieve.RetrieveSettings.SEARCH_FILTER_MODE; @@ -38,6 +40,7 @@ import org.opencds.cqf.fhir.cr.helpers.DataRequirementsLibrary; import org.opencds.cqf.fhir.cr.helpers.GeneratedPackage; import org.opencds.cqf.fhir.utility.Ids; +import org.opencds.cqf.fhir.utility.adapter.IAdapterFactory; import org.opencds.cqf.fhir.utility.model.FhirModelResolverCache; import org.opencds.cqf.fhir.utility.monad.Eithers; import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository; @@ -288,7 +291,8 @@ public static class Evaluation { final IBaseParameters result; final IParser jsonParser; final ModelResolver modelResolver; - final List parameter; + final List parameter; + final IAdapterFactory adapterFactory; @SuppressWarnings("unchecked") public Evaluation(IRepository repository, IBaseParameters result) { @@ -297,7 +301,8 @@ public Evaluation(IRepository repository, IBaseParameters result) { jsonParser = this.repository.fhirContext().newJsonParser().setPrettyPrint(true); modelResolver = FhirModelResolverCache.resolverForVersion( this.repository.fhirContext().getVersion().getVersion()); - parameter = ((List) modelResolver.resolvePath(result, "parameter")); + adapterFactory = IAdapterFactory.forFhirContext(this.repository.fhirContext()); + parameter = ((List) modelResolver.resolvePath(result, "parameter")); } public Evaluation hasResults(Integer count) { @@ -310,9 +315,16 @@ public Evaluation hasOperationOutcome() { return this; } + @SuppressWarnings("unchecked") public Evaluation resultHasValue(Integer index, IBase value) { - var actual = parameter.get(index); - assertEquals(value, actual); + var actual = adapterFactory + .createParametersParameter(parameter.get(index)) + .getValue(); + if (value instanceof IPrimitiveType primitiveValue) { + assertEquals(primitiveValue.getValueAsString(), ((IPrimitiveType) actual).getValueAsString()); + } else { + assertInstanceOf(value.getClass(), actual); + } return this; } } diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/shared/r4/input/cql/HelloWorld2.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/shared/r4/input/cql/HelloWorld2.cql new file mode 100644 index 0000000000..20259f854c --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/shared/r4/input/cql/HelloWorld2.cql @@ -0,0 +1,29 @@ +library HelloWorld version '2.0.0' + +using FHIR version '4.0.1' + +include FHIRHelpers version '4.0.1' + + +context Patient + +define "Info": + 'info' + +define "Warning": + 'warning' + +define "Critical": + 'critical' + +define "Main Action Condition Expression Is True": + true + +define "Get Title": + 'Hello World! I am a new version!' + +define "Get Description": + 'The CDS Service is alive and communicating successfully!' + +define "Get Indicator": + 'info' \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/shared/r4/input/resources/Library-BadLibrary.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/shared/r4/input/resources/Library-BadLibrary.json index 5b810bce20..5cc3f69139 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/shared/r4/input/resources/Library-BadLibrary.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/shared/r4/input/resources/Library-BadLibrary.json @@ -11,7 +11,7 @@ ], "url": "http://fhir.org/guides/cdc/opioid-cds/Library/BadLibrary", "version": "1.0.0", - "name": "HelloWorld", + "name": "NotACorrectLibraryName", "relatedArtifact": [ { "type": "depends-on", diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/shared/r4/input/resources/Library-HelloWorld2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/shared/r4/input/resources/Library-HelloWorld2.json new file mode 100644 index 0000000000..65bbffd333 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/shared/r4/input/resources/Library-HelloWorld2.json @@ -0,0 +1,94 @@ +{ + "resourceType": "Library", + "id": "HelloWorld2", + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-softwaresystem", + "valueReference": { + "reference": "Device/cqf-tooling" + } + } + ], + "url": "http://fhir.org/guides/cdc/opioid-cds/Library/HelloWorld", + "version": "2.0.0", + "name": "HelloWorld", + "relatedArtifact": [ + { + "type": "depends-on", + "display": "FHIR model information", + "resource": "http://fhir.org/guides/cqf/common/Library/FHIR-ModelInfo|4.0.1" + } + ], + "parameter": [ + { + "name": "Patient", + "use": "out", + "min": 0, + "max": "1", + "type": "Patient" + }, + { + "name": "Info", + "use": "out", + "min": 0, + "max": "1", + "type": "string" + }, + { + "name": "Warning", + "use": "out", + "min": 0, + "max": "1", + "type": "string" + }, + { + "name": "Critical", + "use": "out", + "min": 0, + "max": "1", + "type": "string" + }, + { + "name": "Main Action Condition Expression Is True", + "use": "out", + "min": 0, + "max": "1", + "type": "boolean" + }, + { + "name": "Get Title", + "use": "out", + "min": 0, + "max": "1", + "type": "string" + }, + { + "name": "Get Description", + "use": "out", + "min": 0, + "max": "1", + "type": "string" + }, + { + "name": "Get Indicator", + "use": "out", + "min": 0, + "max": "1", + "type": "string" + } + ], + "dataRequirement": [ + { + "type": "Patient", + "profile": [ + "http://hl7.org/fhir/StructureDefinition/Patient" + ] + } + ], + "content": [ + { + "contentType": "text/cql", + "url": "../cql/HelloWorld2.cql" + } + ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/shared/r4/input/resources/PlanDefinition-hello-world-patient-view.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/shared/r4/input/resources/PlanDefinition-hello-world-patient-view.json index 7c721e601f..bed62114f4 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/shared/r4/input/resources/PlanDefinition-hello-world-patient-view.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/shared/r4/input/resources/PlanDefinition-hello-world-patient-view.json @@ -56,7 +56,7 @@ "usage": "This is to be used in conjunction with a patient-facing FHIR application.", "copyright": "© CDC 2016+.", "library": [ - "http://fhir.org/guides/cdc/opioid-cds/Library/HelloWorld" + "http://fhir.org/guides/cdc/opioid-cds/Library/HelloWorld|1.0.0" ], "action": [ { diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/SearchHelper.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/SearchHelper.java index 0d6807089d..6cf19ccf36 100644 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/SearchHelper.java +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/SearchHelper.java @@ -23,6 +23,7 @@ import org.opencds.cqf.fhir.utility.adapter.IDependencyInfo; import org.opencds.cqf.fhir.utility.search.Searches; +@SuppressWarnings("UnstableApiUsage") public class SearchHelper { private SearchHelper() {} @@ -297,7 +298,9 @@ public static > IBaseBundle searchR * @return */ public static IBaseBundle searchRepositoryByCanonicalWithPagingWithParams( - IRepository repository, String canonical, Map> additionalSearchParams) { + IRepository repository, + String canonical, + Multimap> additionalSearchParams) { var resourceType = getResourceType(repository, canonical); return searchRepositoryByCanonicalWithPagingWithParams( repository, canonical, resourceType, additionalSearchParams); @@ -335,16 +338,14 @@ IBaseBundle searchRepositoryByCanonicalWithPagingWithParams( IRepository repository, CanonicalType canonical, Class resourceType, - Map> additionalSearchParams) { + Multimap> additionalSearchParams) { var url = Canonicals.getUrl(canonical); var version = Canonicals.getVersion(canonical); var searchParams = version == null ? Searches.byUrl(url) : Searches.byUrlAndVersion(url, version); if (additionalSearchParams != null) { searchParams.putAll(additionalSearchParams); } - var searchResult = searchRepositoryWithPaging(repository, resourceType, searchParams, Collections.emptyMap()); - - return searchResult; + return searchRepositoryWithPaging(repository, resourceType, searchParams, Collections.emptyMap()); } /** @@ -375,16 +376,14 @@ public static IBaseBundle searchRepositoryByCanonicalW IRepository repository, String canonical, Class resourceType, - Map> additionalSearchParams) { + Multimap> additionalSearchParams) { var url = Canonicals.getUrl(canonical); var version = Canonicals.getVersion(canonical); var searchParams = version == null ? Searches.byUrl(url) : Searches.byUrlAndVersion(url, version); if (additionalSearchParams != null) { searchParams.putAll(additionalSearchParams); } - var searchResult = searchRepositoryWithPaging(repository, resourceType, searchParams, Collections.emptyMap()); - - return searchResult; + return searchRepositoryWithPaging(repository, resourceType, searchParams, Collections.emptyMap()); } /** diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/client/terminology/FederatedTerminologyProviderRouter.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/client/terminology/FederatedTerminologyProviderRouter.java index 2123b0cdd1..9f2c18c5ff 100644 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/client/terminology/FederatedTerminologyProviderRouter.java +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/client/terminology/FederatedTerminologyProviderRouter.java @@ -136,7 +136,7 @@ public Optional getCodeSystemResource(IEndpointAdapter endpoint return IKnowledgeArtifactAdapter.findLatestVersion(client.initializeClientWithAuth(endpoint) .search() .forResource(client.getCodeSystemClass()) - .where(Searches.byCanonical(url)) + .where(Searches.toFlattenedMap(Searches.byCanonical(url))) .execute()); } @@ -154,7 +154,7 @@ public Optional getLatestValueSetResource(IEndpointAdapter endp return IKnowledgeArtifactAdapter.findLatestVersion(client.initializeClientWithAuth(endpoint) .search() .forResource(client.getValueSetClass()) - .where(Searches.byCanonical(url)) + .where(Searches.toFlattenedMap(Searches.byCanonical(url))) .execute()); } @@ -181,7 +181,7 @@ public Optional getValueSetResource(IEndpointAdapter endpoint, return IKnowledgeArtifactAdapter.findLatestVersion(client.initializeClientWithAuth(endpoint) .search() .forResource(client.getValueSetClass()) - .where(Searches.byCanonical(url)) + .where(Searches.toFlattenedMap(Searches.byCanonical(url))) .execute()); } diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/client/terminology/GenericTerminologyServerClient.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/client/terminology/GenericTerminologyServerClient.java index 79f2896b3c..71cac982ed 100644 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/client/terminology/GenericTerminologyServerClient.java +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/client/terminology/GenericTerminologyServerClient.java @@ -120,13 +120,13 @@ public java.util.Optional getCodeSystemResource(IEndpointAdapte return IKnowledgeArtifactAdapter.findLatestVersion(initializeClientWithAuth(endpoint) .search() .forResource(getCodeSystemClass()) - .where(Searches.byCanonical(url)) + .where(Searches.toFlattenedMap(Searches.byCanonical(url))) .execute()); } @Override public java.util.Optional getLatestValueSetResource(IEndpointAdapter endpoint, String url) { - var urlParams = Searches.byCanonical(url); + var urlParams = Searches.toFlattenedMap(Searches.byCanonical(url)); return IKnowledgeArtifactAdapter.findLatestVersion(initializeClientWithAuth(endpoint) .search() .forResource(getValueSetClass()) @@ -139,7 +139,7 @@ public java.util.Optional getValueSetResource(IEndpointAdapter return IKnowledgeArtifactAdapter.findLatestVersion(initializeClientWithAuth(endpoint) .search() .forResource(getValueSetClass()) - .where(Searches.byCanonical(url)) + .where(Searches.toFlattenedMap(Searches.byCanonical(url))) .execute()); } } diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/search/Searches.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/search/Searches.java index ace54eacc3..a1aa3f62f4 100644 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/search/Searches.java +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/search/Searches.java @@ -45,28 +45,19 @@ public static SearchBuilder builder() { return new SearchBuilder(); } - public static Map> byId(String... ids) { - Multimap> multimap = - builder().withTokenParam("_id", ids).build(); - - return toFlattenedMap(multimap); + public static Multimap> byId(String... ids) { + return builder().withTokenParam("_id", ids).build(); } - public static Map> byId(String id) { - Multimap> multimap = - builder().withTokenParam("_id", id).build(); - - return toFlattenedMap(multimap); + public static Multimap> byId(String id) { + return builder().withTokenParam("_id", id).build(); } - public static Map> byProfile(String profile) { - Multimap> multimap = - builder().withProfile(profile).build(); - - return toFlattenedMap(multimap); + public static Multimap> byProfile(String profile) { + return builder().withProfile(profile).build(); } - public static Map> byCanonical(String canonical) { + public static Multimap> byCanonical(String canonical) { var parts = Canonicals.getParts(canonical); if (parts.version() != null) { return byUrlAndVersion(parts.url(), parts.version()); @@ -75,52 +66,36 @@ public static Map> byCanonical(String canonica } } - public static Map> byCodeAndSystem(String code, String system) { - Multimap> multimap = - builder().withTokenParam("code", code, system).build(); - - return toFlattenedMap(multimap); + public static Multimap> byCodeAndSystem(String code, String system) { + return builder().withTokenParam("code", code, system).build(); } - public static Map> byUrl(String url) { - Multimap> multimap = - builder().withUriParam("url", url).build(); - - return toFlattenedMap(multimap); + public static Multimap> byUrl(String url) { + return builder().withUriParam("url", url).build(); } - public static Map> byUrlAndVersion(String url, String version) { - Multimap> multimap = builder() + public static Multimap> byUrlAndVersion(String url, String version) { + return builder() .withUriParam("url", url) .withTokenParam("version", version) .build(); - - return toFlattenedMap(multimap); } - public static Map> byName(String name) { - Multimap> multimap = - builder().withStringParam("name", name).build(); - - return toFlattenedMap(multimap); + public static Multimap> byName(String name) { + return builder().withStringParam("name", name).build(); } - public static Map> byStatus(String status) { - Multimap> multimap = - builder().withTokenParam("status", status).build(); - - return toFlattenedMap(multimap); + public static Multimap> byStatus(String status) { + return builder().withTokenParam("status", status).build(); } - public static Map> exceptStatus(String status) { - Multimap> multimap = builder() + public static Multimap> exceptStatus(String status) { + return builder() .withModifiedTokenParam("status", TokenParamModifier.NOT, status) .build(); - - return toFlattenedMap(multimap); } - public static Map> byNameAndVersion(String name, String version) { + public static Multimap> byNameAndVersion(String name, String version) { Multimap> multimap; if (version == null || version.isEmpty()) { @@ -132,7 +107,7 @@ public static Map> byNameAndVersion(String nam .build(); } - return toFlattenedMap(multimap); + return multimap; } public static Map> toFlattenedMap( diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/SearchHelperTest.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/SearchHelperTest.java index a72c3df622..d07a799d90 100644 --- a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/SearchHelperTest.java +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/SearchHelperTest.java @@ -8,6 +8,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.repository.IRepository; +import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import org.hl7.fhir.r4.model.ValueSet; import org.junit.jupiter.api.BeforeEach; @@ -138,7 +139,7 @@ void searchRepositoryByCanonicalR4() { var bundle = new org.hl7.fhir.r4.model.Bundle(); bundle.addEntry().setResource(vs); // searchRepositoryByCanonical calls the 3-arg search (no headers) - when(r4mockRepository.search(any(), any(), any(java.util.Map.class))).thenReturn(bundle); + when(r4mockRepository.search(any(), any(), any(Multimap.class))).thenReturn(bundle); var canonical = new org.hl7.fhir.r4.model.CanonicalType("http://example.org/ValueSet/test"); var result = SearchHelper.searchRepositoryByCanonical(r4mockRepository, canonical); assertEquals("test-vs", result.getIdElement().getIdPart()); @@ -192,7 +193,7 @@ void searchRepositoryByCanonicalWithPagingString() { void searchRepositoryByCanonicalWithPagingWithParams() { var repo = fullMockR4(); var result = SearchHelper.searchRepositoryByCanonicalWithPagingWithParams( - repo, "http://example.org/ValueSet/test|1.0", java.util.Collections.emptyMap()); + repo, "http://example.org/ValueSet/test|1.0", ArrayListMultimap.create()); assertEquals(1, BundleHelper.getEntryResources(result).size()); } diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/r4/VersionSpecificHelperTest.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/r4/VersionSpecificHelperTest.java index b915a00c97..b4bb03b5f1 100644 --- a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/r4/VersionSpecificHelperTest.java +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/r4/VersionSpecificHelperTest.java @@ -9,6 +9,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.repository.IRepository; +import com.google.common.collect.Multimap; import java.util.Collections; import java.util.Map; import org.hl7.fhir.exceptions.FHIRException; @@ -32,7 +33,7 @@ void searchRepositoryByCanonicalR4() { var vs = new ValueSet().setId("test-vs"); var bundle = new Bundle(); bundle.addEntry().setResource(vs); - when(repo.search(any(), any(), any(Map.class))).thenReturn(bundle); + when(repo.search(any(), any(), any(Multimap.class))).thenReturn(bundle); var result = SearchHelper.searchRepositoryByCanonical( repo, new org.hl7.fhir.r4.model.CanonicalType("http://example.org/ValueSet/test")); assertEquals("test-vs", result.getIdElement().getIdPart()); @@ -44,7 +45,7 @@ void searchRepositoryByCanonicalWithVersionR4() { var vs = new ValueSet().setId("test-vs"); var bundle = new Bundle(); bundle.addEntry().setResource(vs); - when(repo.search(any(), any(), any(Map.class))).thenReturn(bundle); + when(repo.search(any(), any(), any(Multimap.class))).thenReturn(bundle); var result = SearchHelper.searchRepositoryByCanonical( repo, new org.hl7.fhir.r4.model.CanonicalType("http://example.org/ValueSet/test|1.0")); assertNotNull(result); @@ -53,7 +54,7 @@ void searchRepositoryByCanonicalWithVersionR4() { @Test void searchRepositoryByCanonicalNotFoundThrows() { var repo = mockR4Repo(); - when(repo.search(any(), any(), any(Map.class))).thenReturn(new Bundle()); + when(repo.search(any(), any(), any(Multimap.class))).thenReturn(new Bundle()); assertThrows( FHIRException.class, () -> SearchHelper.searchRepositoryByCanonical( diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/repository/ig/IgRepositoryTransactionTest.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/repository/ig/IgRepositoryTransactionTest.java index 1945f2dd43..c3118194e8 100644 --- a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/repository/ig/IgRepositoryTransactionTest.java +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/repository/ig/IgRepositoryTransactionTest.java @@ -13,6 +13,7 @@ import java.net.URISyntaxException; import java.nio.file.Path; import java.util.Collections; +import java.util.Map; import java.util.stream.Stream; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Bundle; @@ -31,6 +32,8 @@ class IgRepositoryTransactionTest { + private static final Map HEADERS_EMPTY = Collections.emptyMap(); + private static IgRepository repository; @TempDir @@ -117,7 +120,7 @@ void transactionPost_createsResourceVisibleInReadAndSearch(ResourceFixture fixtu .setMethod(Bundle.HTTPVerb.POST) .setUrl(fixture.resourceType); - var result = repository.transaction(txBundle, Collections.emptyMap()); + var result = repository.transaction(txBundle, HEADERS_EMPTY); assertNotNull(result); assertEquals(1, result.getEntry().size()); @@ -141,7 +144,7 @@ void transactionPost_assignsIdWhenMissing(ResourceFixture fixture) { .setMethod(Bundle.HTTPVerb.POST) .setUrl(fixture.resourceType); - repository.transaction(txBundle, Collections.emptyMap()); + repository.transaction(txBundle, HEADERS_EMPTY); assertTrue(resource.getIdElement().hasIdPart()); var read = repository.read(fixture.resourceClass, resource.getIdElement()); @@ -161,7 +164,7 @@ void transactionPut_updatesResourceVisibleInReadAndSearch(ResourceFixture fixtur .setMethod(Bundle.HTTPVerb.PUT) .setUrl(fixture.resourceType + "/" + id); - var result = repository.transaction(txBundle, Collections.emptyMap()); + var result = repository.transaction(txBundle, HEADERS_EMPTY); assertNotNull(result); assertEquals(1, result.getEntry().size()); @@ -182,7 +185,7 @@ void transactionPut_overwritesExistingResource(ResourceFixture fixture) { .getRequest() .setMethod(Bundle.HTTPVerb.POST) .setUrl(fixture.resourceType); - repository.transaction(postBundle, Collections.emptyMap()); + repository.transaction(postBundle, HEADERS_EMPTY); var updated = fixture.create(id, id + "-v2"); @@ -193,7 +196,7 @@ void transactionPut_overwritesExistingResource(ResourceFixture fixture) { .getRequest() .setMethod(Bundle.HTTPVerb.PUT) .setUrl(fixture.resourceType + "/" + id); - repository.transaction(putBundle, Collections.emptyMap()); + repository.transaction(putBundle, HEADERS_EMPTY); var searchV1 = repository.search(Bundle.class, fixture.resourceClass, Searches.byUrl(fixture.urlPrefix + id + "-v1")); @@ -217,7 +220,7 @@ void transactionDelete_removesResource(ResourceFixture fixture) { .getRequest() .setMethod(Bundle.HTTPVerb.POST) .setUrl(fixture.resourceType); - repository.transaction(postBundle, Collections.emptyMap()); + repository.transaction(postBundle, HEADERS_EMPTY); var read = repository.read(fixture.resourceClass, Ids.newId(fixture.resourceClass, id)); assertNotNull(read); @@ -225,7 +228,7 @@ void transactionDelete_removesResource(ResourceFixture fixture) { var deleteBundle = new Bundle().setType(Bundle.BundleType.TRANSACTION); deleteBundle.addEntry().getRequest().setMethod(Bundle.HTTPVerb.DELETE).setUrl(fixture.resourceType + "/" + id); - var result = repository.transaction(deleteBundle, Collections.emptyMap()); + var result = repository.transaction(deleteBundle, HEADERS_EMPTY); assertNotNull(result); assertEquals(1, result.getEntry().size()); @@ -244,8 +247,7 @@ void transactionDelete_throwsWhenNotFound(ResourceFixture fixture) { .setMethod(Bundle.HTTPVerb.DELETE) .setUrl(fixture.resourceType + "/does-not-exist-" + fixture.label); - assertThrows( - ResourceNotFoundException.class, () -> repository.transaction(deleteBundle, Collections.emptyMap())); + assertThrows(ResourceNotFoundException.class, () -> repository.transaction(deleteBundle, HEADERS_EMPTY)); } @ParameterizedTest(name = "{0}") @@ -268,7 +270,7 @@ void transactionMixed_postThenPutThenDelete(ResourceFixture fixture) { .getRequest() .setMethod(Bundle.HTTPVerb.POST) .setUrl(fixture.resourceType); - repository.transaction(postBundle, Collections.emptyMap()); + repository.transaction(postBundle, HEADERS_EMPTY); var res1Updated = fixture.create("tx-mixed-1-" + suffix, "tx-mixed-1-" + suffix + "-updated"); var res3 = fixture.create("tx-mixed-3-" + suffix, "tx-mixed-3-" + suffix); @@ -292,7 +294,7 @@ void transactionMixed_postThenPutThenDelete(ResourceFixture fixture) { .setMethod(Bundle.HTTPVerb.POST) .setUrl(fixture.resourceType); - var result = repository.transaction(mixedBundle, Collections.emptyMap()); + var result = repository.transaction(mixedBundle, HEADERS_EMPTY); assertEquals(3, result.getEntry().size()); var readRes1 = repository.read(fixture.resourceClass, Ids.newId(fixture.resourceClass, "tx-mixed-1-" + suffix)); @@ -317,7 +319,6 @@ void transactionUnsupportedMethod_throws() { var txBundle = new Bundle().setType(Bundle.BundleType.TRANSACTION); txBundle.addEntry().getRequest().setMethod(Bundle.HTTPVerb.GET).setUrl("Library/123"); - assertThrows( - NotImplementedOperationException.class, () -> repository.transaction(txBundle, Collections.emptyMap())); + assertThrows(NotImplementedOperationException.class, () -> repository.transaction(txBundle, HEADERS_EMPTY)); } } diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/search/SearchesTest.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/search/SearchesTest.java index 9b1e90d626..00304f5718 100644 --- a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/search/SearchesTest.java +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/search/SearchesTest.java @@ -25,14 +25,16 @@ void byIdSingle() { assertEquals(1, result.size()); assertTrue(result.containsKey("_id")); assertEquals(1, result.get("_id").size()); - assertTrue(result.get("_id").get(0) instanceof TokenParam); + assertEquals(1, result.get("_id").stream().toList().get(0).size()); + assertTrue(result.get("_id").stream().toList().get(0).get(0) instanceof TokenParam); } @Test void byIdMultiple() { var result = Searches.byId("1", "2", "3"); assertEquals(1, result.size()); - assertEquals(3, result.get("_id").size()); + assertEquals(1, result.get("_id").size()); + assertEquals(3, result.get("_id").stream().toList().get(0).size()); } @Test @@ -40,7 +42,8 @@ void byProfile() { var result = Searches.byProfile("http://example.org/profile"); assertEquals(1, result.size()); assertTrue(result.containsKey("_profile")); - assertTrue(result.get("_profile").get(0) instanceof UriParam); + assertEquals(1, result.get("_profile").stream().toList().get(0).size()); + assertTrue(result.get("_profile").stream().toList().get(0).get(0) instanceof UriParam); } @Test @@ -61,7 +64,7 @@ void byCanonicalWithoutVersion() { void byCodeAndSystem() { var result = Searches.byCodeAndSystem("test-code", "http://example.org"); assertTrue(result.containsKey("code")); - var param = (TokenParam) result.get("code").get(0); + var param = (TokenParam) result.get("code").stream().toList().get(0).get(0); assertEquals("test-code", param.getValue()); assertEquals("http://example.org", param.getSystem()); } @@ -70,7 +73,7 @@ void byCodeAndSystem() { void byUrl() { var result = Searches.byUrl("http://example.org/Library/test"); assertTrue(result.containsKey("url")); - assertTrue(result.get("url").get(0) instanceof UriParam); + assertTrue(result.get("url").stream().toList().get(0).get(0) instanceof UriParam); } @Test @@ -84,14 +87,14 @@ void byUrlAndVersion() { void byName() { var result = Searches.byName("TestLibrary"); assertTrue(result.containsKey("name")); - assertTrue(result.get("name").get(0) instanceof StringParam); + assertTrue(result.get("name").stream().toList().get(0).get(0) instanceof StringParam); } @Test void byStatus() { var result = Searches.byStatus("active"); assertTrue(result.containsKey("status")); - var param = (TokenParam) result.get("status").get(0); + var param = (TokenParam) result.get("status").stream().toList().get(0).get(0); assertEquals("active", param.getValue()); } @@ -99,7 +102,7 @@ void byStatus() { void exceptStatus() { var result = Searches.exceptStatus("retired"); assertTrue(result.containsKey("status")); - var param = (TokenParam) result.get("status").get(0); + var param = (TokenParam) result.get("status").stream().toList().get(0).get(0); assertEquals("retired", param.getValue()); assertEquals(TokenParamModifier.NOT, param.getModifier()); }