From 63eed3936ba25464412509e976e19290760ab991 Mon Sep 17 00:00:00 2001 From: Taha Date: Mon, 27 Nov 2023 20:12:35 -0800 Subject: [PATCH 01/14] (#5442) fetch should also resolve canonical URL references (#5443) * (#5442) fetch should also resolve canonical URL references * (#5442) - added test * (#5442) - cleanup? * (#5442) - changelog and reorganize test * (#5442) PR feedback * (#5442) PR feedback * (#5442) cleared ValidatorResourceFetcher linter warnings * (#5442) - new error code * (#5442) caught additional error * (#5442) spotless apply * (#5442) spotless apply --------- Co-authored-by: taha.attari@smilecdr.com --- ...validation-fetcher-fetch-by-canonical.yaml | 4 + .../fhir/jpa/api/dao/IFhirResourceDao.java | 21 +- .../validation/ValidatorResourceFetcher.java | 60 +- .../ValidatorResourceFetcherTest.java | 65 + .../resources/q_jon_with_url_version.json | 1388 +++++++++++++++++ 5 files changed, 1516 insertions(+), 22 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5442-validation-fetcher-fetch-by-canonical.yaml create mode 100644 hapi-fhir-storage/src/test/java/ca/uhn/fhir/jpa/validation/ValidatorResourceFetcherTest.java create mode 100644 hapi-fhir-storage/src/test/resources/q_jon_with_url_version.json diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5442-validation-fetcher-fetch-by-canonical.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5442-validation-fetcher-fetch-by-canonical.yaml new file mode 100644 index 00000000000..226bb7cd153 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5442-validation-fetcher-fetch-by-canonical.yaml @@ -0,0 +1,4 @@ +--- +type: add +issue: 5442 +title: "The ValidatorResourceFetcher will now resolve canonical URL references as well as simple local references." diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDao.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDao.java index 171e5cfeb8e..176983551fb 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDao.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDao.java @@ -327,18 +327,29 @@ public interface IFhirResourceDao extends IDao { /** * @deprecated Use {@link #search(SearchParameterMap, RequestDetails)} instead + * @throws InvalidRequestException If a SearchParameter is not known to the server */ - IBundleProvider search(SearchParameterMap theParams); + IBundleProvider search(SearchParameterMap theParams) throws InvalidRequestException; - IBundleProvider search(SearchParameterMap theParams, RequestDetails theRequestDetails); + /** + * * + * @throws InvalidRequestException If a SearchParameter is not known to the server + */ + IBundleProvider search(SearchParameterMap theParams, RequestDetails theRequestDetails) + throws InvalidRequestException; + /** + * * + * @throws InvalidRequestException If a SearchParameter is not known to the server + */ IBundleProvider search( - SearchParameterMap theParams, RequestDetails theRequestDetails, HttpServletResponse theServletResponse); + SearchParameterMap theParams, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) + throws InvalidRequestException; /** * Search for IDs for processing a match URLs, etc. */ - default List searchForIds( + default List searchForIds( SearchParameterMap theParams, RequestDetails theRequest) { return searchForIds(theParams, theRequest, null); } @@ -350,7 +361,7 @@ public interface IFhirResourceDao extends IDao { * create/update, this is the resource being searched for * @since 5.5.0 */ - default List searchForIds( + default List searchForIds( SearchParameterMap theParams, RequestDetails theRequest, @Nullable IBaseResource theConditionalOperationTargetOrNull) { diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/validation/ValidatorResourceFetcher.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/validation/ValidatorResourceFetcher.java index d7fc7b40ab7..f988532989a 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/validation/ValidatorResourceFetcher.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/validation/ValidatorResourceFetcher.java @@ -24,12 +24,14 @@ import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.param.UriParam; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import org.hl7.fhir.common.hapi.validation.validator.VersionSpecificWorkerContextWrapper; -import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r5.elementmodel.Element; @@ -37,12 +39,11 @@ import org.hl7.fhir.r5.elementmodel.JsonParser; import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.utils.validation.IResourceValidator; import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; +import org.hl7.fhir.utilities.CanonicalPair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URISyntaxException; +import java.util.List; import java.util.Locale; public class ValidatorResourceFetcher implements IValidatorResourceFetcher { @@ -50,22 +51,19 @@ public class ValidatorResourceFetcher implements IValidatorResourceFetcher { private static final Logger ourLog = LoggerFactory.getLogger(ValidatorResourceFetcher.class); private final FhirContext myFhirContext; - private final IValidationSupport myValidationSupport; private final DaoRegistry myDaoRegistry; private final VersionSpecificWorkerContextWrapper myVersionSpecificContextWrapper; public ValidatorResourceFetcher( FhirContext theFhirContext, IValidationSupport theValidationSupport, DaoRegistry theDaoRegistry) { myFhirContext = theFhirContext; - myValidationSupport = theValidationSupport; myDaoRegistry = theDaoRegistry; myVersionSpecificContextWrapper = - VersionSpecificWorkerContextWrapper.newVersionSpecificWorkerContextWrapper(myValidationSupport); + VersionSpecificWorkerContextWrapper.newVersionSpecificWorkerContextWrapper(theValidationSupport); } @Override - public Element fetch(IResourceValidator iResourceValidator, Object appContext, String theUrl) - throws FHIRFormatError, DefinitionException, FHIRException, IOException { + public Element fetch(IResourceValidator iResourceValidator, Object appContext, String theUrl) throws FHIRException { IdType id = new IdType(theUrl); String resourceType = id.getResourceType(); IFhirResourceDao dao = myDaoRegistry.getResourceDao(resourceType); @@ -74,9 +72,13 @@ public class ValidatorResourceFetcher implements IValidatorResourceFetcher { target = dao.read(id, (RequestDetails) appContext); } catch (ResourceNotFoundException e) { ourLog.info("Failed to resolve local reference: {}", theUrl); - return null; + try { + target = fetchByUrl(theUrl, dao, (RequestDetails) appContext); + } catch (ResourceNotFoundException e2) { + ourLog.info("Failed to find resource by URL: {}", theUrl); + return null; + } } - try { return new JsonParser(myVersionSpecificContextWrapper) .parse(myFhirContext.newJsonParser().encodeResourceToString(target), resourceType); @@ -85,15 +87,40 @@ public class ValidatorResourceFetcher implements IValidatorResourceFetcher { } } + private IBaseResource fetchByUrl(String url, IFhirResourceDao dao, RequestDetails requestDetails) + throws ResourceNotFoundException { + CanonicalPair pair = new CanonicalPair(url); + SearchParameterMap searchParameterMap = new SearchParameterMap(); + searchParameterMap.add("url", new UriParam(pair.getUrl())); + String version = pair.getVersion(); + if (version != null && !version.isEmpty()) { + searchParameterMap.add("version", new TokenParam(version)); + } + List results = null; + try { + results = dao.search(searchParameterMap, requestDetails).getAllResources(); + } catch (InvalidRequestException e) { + ourLog.info("Resource does not support 'url' or 'version' Search Parameters"); + } + if (results != null && results.size() > 0) { + if (results.size() > 1) { + ourLog.warn( + String.format("Multiple results found for URL '%s', only the first will be considered.", url)); + } + return results.get(0); + } else { + throw new ResourceNotFoundException(Msg.code(2444) + "Failed to find resource by URL: " + url); + } + } + @Override public boolean resolveURL( - IResourceValidator iResourceValidator, Object o, String s, String s1, String s2, boolean isCanonical) - throws IOException, FHIRException { + IResourceValidator iResourceValidator, Object o, String s, String s1, String s2, boolean isCanonical) { return true; } @Override - public byte[] fetchRaw(IResourceValidator iResourceValidator, String s) throws MalformedURLException, IOException { + public byte[] fetchRaw(IResourceValidator iResourceValidator, String s) throws UnsupportedOperationException { throw new UnsupportedOperationException(Msg.code(577)); } @@ -104,8 +131,7 @@ public class ValidatorResourceFetcher implements IValidatorResourceFetcher { } @Override - public CanonicalResource fetchCanonicalResource(IResourceValidator iResourceValidator, String s) - throws URISyntaxException { + public CanonicalResource fetchCanonicalResource(IResourceValidator iResourceValidator, String s) { return null; } diff --git a/hapi-fhir-storage/src/test/java/ca/uhn/fhir/jpa/validation/ValidatorResourceFetcherTest.java b/hapi-fhir-storage/src/test/java/ca/uhn/fhir/jpa/validation/ValidatorResourceFetcherTest.java new file mode 100644 index 00000000000..9009f6137fa --- /dev/null +++ b/hapi-fhir-storage/src/test/java/ca/uhn/fhir/jpa/validation/ValidatorResourceFetcherTest.java @@ -0,0 +1,65 @@ +package ca.uhn.fhir.jpa.validation; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.api.server.SystemRequestDetails; +import ca.uhn.fhir.rest.server.SimpleBundleProvider; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.test.BaseTest; +import ca.uhn.fhir.util.ClasspathUtil; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; +import org.hl7.fhir.common.hapi.validation.validator.VersionSpecificWorkerContextWrapper; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r5.elementmodel.Element; +import org.hl7.fhir.r5.utils.XVerExtensionManager; +import org.hl7.fhir.validation.instance.InstanceValidator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; + + +public class ValidatorResourceFetcherTest extends BaseTest { + private static final FhirContext ourCtx = FhirContext.forR4(); + private static final DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport(ourCtx); + private static ValidatorResourceFetcher fetcher; + private static DaoRegistry mockDaoRegistry; + private static IFhirResourceDao mockResourceDao; + + @SuppressWarnings("unchecked") + @BeforeEach + public void before() { + mockDaoRegistry = mock(DaoRegistry.class); + mockResourceDao = mock(IFhirResourceDao.class); + fetcher = new ValidatorResourceFetcher(ourCtx, myDefaultValidationSupport, mockDaoRegistry); + } + + @Test + public void checkFetchByUrl() { + // setup mocks + String resource = ClasspathUtil.loadResource("/q_jon_with_url_version.json"); + doReturn(mockResourceDao).when(mockDaoRegistry).getResourceDao("Questionnaire"); + doThrow(new ResourceNotFoundException("Not Found")).when(mockResourceDao).read(any(),any()); + doReturn(new SimpleBundleProvider(List.of( + ourCtx.newJsonParser().parseResource(resource) + ))).when(mockResourceDao).search(any(),any()); + VersionSpecificWorkerContextWrapper wrappedWorkerContext = VersionSpecificWorkerContextWrapper.newVersionSpecificWorkerContextWrapper(myDefaultValidationSupport); + InstanceValidator v = new InstanceValidator( + wrappedWorkerContext, + new FhirInstanceValidator.NullEvaluationContext(), + new XVerExtensionManager(null)); + RequestDetails r = new SystemRequestDetails(); + // test + Element returnedResource = fetcher.fetch(v, r,"http://www.test-url-for-questionnaire.com/Questionnaire/test-id|1.0.0"); + assertNotNull(returnedResource); + } +} diff --git a/hapi-fhir-storage/src/test/resources/q_jon_with_url_version.json b/hapi-fhir-storage/src/test/resources/q_jon_with_url_version.json new file mode 100644 index 00000000000..7ca33e5e290 --- /dev/null +++ b/hapi-fhir-storage/src/test/resources/q_jon_with_url_version.json @@ -0,0 +1,1388 @@ +{ + "resourceType": "Questionnaire", + "status": "draft", + "date": "2015-10-29", + "publisher": "Cancer Care Ontario", + "id": "test-id", + "telecom": [ + { + "system": "email", + "value": "jon.zammit@cancercare.on.ca" + } + ], + "title": "CT Lung for Cancer Staging Template - DRAFT (Short Version)", + "url": "http://www.test-url-for-questionnaire.com/Questionnaire/test-id", + "version": "1.0.0", + "item": [ + { + "text": "Patient with high suspicion of cancer as per the PEBC document (EBS #24-2) or radiological/laboratory tests suggesting cancer. Excluding: patients with synchronous lung primary, previous diagnosis of lung cancer, lung cancer surgery or therapy. New single lung primary only.", + "type": "display" + }, + { + "linkId": "root", + "type": "group", + "required": true, + "item": [ + { + "linkId": "g1", + "concept": [ + { + "system": "http://loinc.org", + "code": "55752-0", + "display": "Clinical Information" + } + ], + "text": "CLINICAL INFORMATION", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "1.1" + } + ], + "linkId": "1.1", + "text": "Patient Clinical Information", + "type": "text" + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "1.2" + } + ], + "linkId": "1.2", + "text": "Previous Examination (Date and Modality)", + "type": "text" + } + ] + }, + { + "linkId": "g2", + "text": "IMAGING PROCEDURE DESCRIPTION", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "2.1" + } + ], + "linkId": "2.1", + "text": "Overall Image Quality:", + "type": "choice", + "option": [ + { + "valueCoding": { + "code": "2.1a", + "display": "Adequate" + } + }, + { + "valueCoding": { + "code": "2.1b", + "display": "Suboptimal" + } + }, + { + "valueCoding": { + "code": "2.1c", + "display": "Non-diagnostic" + } + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "2.2" + } + ], + "linkId": "2.2", + "text": "Intravenous Contrast Used?", + "type": "boolean" + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "2.3" + } + ], + "linkId": "2.3", + "text": "Additional Comments", + "type": "text" + } + ] + }, + { + "linkId": "g3", + "text": "FINDINGS", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3" + } + ], + "linkId": "g3.0", + "text": "T Category", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.1" + } + ], + "linkId": "g3.1", + "text": "Location of Main Nodule/Mass (Primary tumor, or Reference tumor)", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.1.1" + } + ], + "linkId": "q3.1.1", + "text": "Location of main nodule/mass:", + "type": "choice", + "option": [ + { + "valueCoding": { + "code": "3.1.1a", + "display": "Peripheral" + } + }, + { + "valueCoding": { + "code": "3.1.1b", + "display": "Central*" + } + }, + { + "valueCoding": { + "code": "3.1.1c", + "display": "Both*" + } + } + ], + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "q3.1.1" + }, + { + "url": "#answer", + "valueCoding": { + "code": "3.1.1b" + } + } + ] + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "q3.1.1" + }, + { + "url": "#answer", + "valueCoding": { + "code": "3.1.1c" + } + } + ] + } + ], + "linkId": "g3.1.1i", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "i)" + } + ], + "linkId": "q3.1.1i", + "text": "What is the distance of the nodule/mass to the carina?", + "type": "integer", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl", + "valueCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/ValueSet/questionnaire-item-control", + "code": "unit" + } + ] + } + } + ], + "text": "mm", + "type": "display" + } + ] + }, + { + "linkId": "3.1.1i.image", + "text": "image", + "type": "string" + }, + { + "linkId": "3.1.1i.series", + "text": "series", + "type": "string" + } + ] + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.1.2" + } + ], + "linkId": "3.1.2", + "text": "State the lobe(s) and segment(s) where the nodule/mass is located", + "type": "text" + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.2" + } + ], + "linkId": "g3.2", + "text": "Size and characteristics of main nodule/mass", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.2.1" + } + ], + "linkId": "3.2.1", + "text": "Size of the nodule/mass:", + "type": "choice", + "option": [ + { + "valueCoding": { + "code": "3.2.1a", + "display": "Solid nodule/mass" + } + }, + { + "valueCoding": { + "code": "3.2.1b", + "display": "Part-solid nodule/mass" + } + }, + { + "valueCoding": { + "code": "3.2.1c", + "display": "Pure Ground glass" + } + } + ], + "item": [ + { + "linkId": "g3.2.1", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "3.2.1" + }, + { + "url": "#answer", + "valueCoding": { + "code": "3.2.1a" + } + } + ] + } + ], + "linkId": "g3.2.1a", + "type": "group", + "item": [ + { + "linkId": "3.2.1a", + "text": "largest dimension:", + "type": "integer", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl", + "valueCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/ValueSet/questionnaire-item-control", + "code": "unit" + } + ] + } + } + ], + "text": "mm", + "type": "display" + } + ] + }, + { + "linkId": "3.2.1a.image", + "text": "image", + "type": "string" + }, + { + "linkId": "3.2.1a.series", + "text": "series", + "type": "string" + } + ] + } + ] + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.2.2" + } + ], + "linkId": "3.2.2", + "text": "Plane in which the mass was measured:", + "type": "choice", + "option": [ + { + "code": "3.2.2a", + "display": "Axial" + }, + { + "code": "3.2.2b", + "display": "Coronal" + }, + { + "code": "3.2.2c", + "display": "Sagittal" + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.2.3" + } + ], + "linkId": "3.2.3", + "text": "Other characteristics of the main nodule/mass:", + "type": "text" + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.3" + } + ], + "linkId": "g3.3", + "text": "Structures directly involved", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.3.1" + } + ], + "linkId": "3.3.1", + "text": "State if there is bronchial involvement:", + "type": "choice", + "option": [ + { + "code": "3.3.1a", + "display": "*Yes" + }, + { + "code": "3.3.1b", + "display": "No" + }, + { + "code": "3.3.1c", + "display": "Uncertain" + } + ], + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory", + "valueCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/ValueSet/questionnaire-display-category", + "code": "instructions" + } + ] + } + } + ], + "text": "*If yes, answer i and ii.", + "type": "display" + }, + { + "linkId": "g3.3.1", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "3.3.1" + }, + { + "url": "#answer", + "valueCoding": { + "code": "3.3.1a" + } + } + ] + } + ], + "linkId": "g3.3.1.yes", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "i)" + } + ], + "linkId": "3.3.1i", + "text": "Type of involvement:", + "type": "text" + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "ii)" + } + ], + "linkId": "3.3.1ii", + "text": "State if there is endobronchial involvement:", + "type": "choice", + "option": [ + { + "code": "3.3.1.iia", + "display": "*Yes" + }, + { + "code": "3.3.1.iib", + "display": "No" + }, + { + "code": "3.3.1.iic", + "display": "Uncertain" + } + ] + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "3.3.1ii" + }, + { + "url": "#answer", + "valueCoding": { + "code": "3.3.1.iia" + } + } + ] + } + ], + "linkId": "3.3.1iic", + "text": "Describe:", + "type": "text" + } + ] + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.3.2" + } + ], + "linkId": "3.3.2", + "text": "Is there direct involvement of any other anatomical structures?", + "type": "boolean", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory", + "valueCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/ValueSet/questionnaire-display-category", + "code": "instructions" + } + ] + } + } + ], + "text": "*If yes, indicate and describe all ipsilateral anatomical structures involved by the mass. If no, go to 3.3.3.", + "type": "display" + }, + { + "linkId": "g3.3.2.yes", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "3.3.2" + }, + { + "url": "#answer", + "valueBoolean": true + } + ] + } + ], + "linkId": "3.3.2.Yes", + "text": "Indicate and describe all ipsilateral anatomical structures involved by the mass:", + "type": "choice", + "repeats": true, + "option": [ + { + "code": "3.3.2.Yes.a", + "display": "Pleura" + }, + { + "code": "3.3.2.Yes.b", + "display": "Brachial Plexus" + }, + { + "code": "3.3.2.Yes.c", + "display": "Diaphragm" + }, + { + "code": "3.3.2.Yes.d", + "display": "Mediastinal fat" + }, + { + "code": "3.3.2.Yes.e", + "display": "Aorta" + }, + { + "code": "3.3.2.Yes.f", + "display": "Pulmonary Vessel" + }, + { + "code": "3.3.2.Yes.g", + "display": "Pericardium or Heart" + }, + { + "code": "3.3.2.Yes.h", + "display": "Mediastinal Vessels (including SVC)" + }, + { + "code": "3.3.2.Yes.i", + "display": "Trachea/carina" + }, + { + "code": "3.3.2.Yes.j", + "display": "Esophagus" + }, + { + "code": "3.3.2.Yes.k", + "display": "Trachea esophageal groove" + }, + { + "code": "3.3.2.Yes.l", + "display": "Vertebral Body" + }, + { + "code": "3.3.2.Yes.m", + "display": "Chest wall and Ribs" + }, + { + "code": "3.3.2.Yes.n", + "display": "Other structures" + } + ], + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "3.3.2.Yes" + }, + { + "url": "#answer", + "valueCoding": { + "code": "3.3.2.Yes.a" + } + } + ] + } + ], + "linkId": "3.3.2.Yes.a.text", + "text": "Pleura, description:", + "type": "string" + } + ] + } + ] + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.3.3" + } + ], + "linkId": "3.3.3", + "text": "Are there additional suspicious pulmonary nodules?", + "type": "boolean", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory", + "valueCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/ValueSet/questionnaire-display-category", + "code": "instructions" + } + ] + } + } + ], + "text": "*If yes, indicate and describe pulmonary nodules which apply. If no, go to 3.3.4.", + "type": "display" + }, + { + "linkId": "g3.3.3.yes", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "3.3.3" + }, + { + "url": "#answer", + "valueBoolean": true + } + ] + } + ], + "linkId": "3.3.3.Yes", + "text": "Indicate and describe pulmonary nodules which apply:", + "type": "choice", + "option": [ + { + "code": "3.3.3.Yes.a", + "display": "In the same lobe" + }, + { + "code": "3.3.3.Yes.b", + "display": "In a different lobe, same lung" + }, + { + "code": "3.3.3.Yes.c", + "display": "In the opposite lung (M1a)" + } + ], + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "3.3.3.Yes" + }, + { + "url": "#answer", + "valueCoding": { + "code": "3.3.3.Yes.a" + } + } + ] + } + ], + "linkId": "3.3.3.Yes.a.text", + "text": "In the same lobe, description:", + "type": "string" + } + ] + } + ] + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.3.4" + } + ], + "linkId": "3.3.4", + "text": "Other notable intrathoracic findings (eg lymphangitis carcinomatosis):", + "type": "text" + } + ] + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "4" + } + ], + "linkId": "4.0", + "text": "N Category", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "4.1" + } + ], + "linkId": "4.1", + "text": "Are there enlarged lymph nodes?", + "type": "boolean", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory", + "valueCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/ValueSet/questionnaire-display-category", + "code": "instructions" + } + ] + } + } + ], + "text": "*If yes, indicate and describe the nodes which apply. If no, go to 4.2.", + "type": "display" + }, + { + "linkId": "g4.1.yes", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "4.1" + }, + { + "url": "#answer", + "valueBoolean": true + } + ] + } + ], + "linkId": "4.1.yes", + "text": "Nodes and Descriptions:", + "type": "choice", + "repeats": true, + "option": [ + { + "code": "4.1.yes.a", + "display": "1 Low Cervical, supraclavicular, and sternal notch nodes" + }, + { + "code": "4.1.yes.b", + "display": "2R Upper Paratracheal (right)" + }, + { + "code": "4.1.yes.c", + "display": "2L Upper paratracheal (left)" + }, + { + "code": "4.1.yes.d", + "display": "3a Pre-vascular" + }, + { + "code": "4.1.yes.e", + "display": "3p Retrotracheal" + }, + { + "code": "4.1.yes.f", + "display": "4R Lower paratracheal (right)" + }, + { + "code": "4.1.yes.g", + "display": "4L Upper paratracheal (left)" + }, + { + "code": "4.1.yes.h", + "display": "5 Subaortic" + }, + { + "code": "4.1.yes.i", + "display": "6 Para-aortic (ascending aorta or phrenic)" + }, + { + "code": "4.1.yes.j", + "display": "7 Subcarinal" + }, + { + "code": "4.1.yes.k", + "display": "8 Paraesophageal (below carina)" + }, + { + "code": "4.1.yes.l", + "display": "9 Pulmonary ligament" + }, + { + "code": "4.1.yes.m", + "display": "10 Hilar" + }, + { + "code": "4.1.yes.n", + "display": "11 Interlobar" + }, + { + "code": "4.1.yes.o", + "display": "12 Lobar" + }, + { + "code": "4.1.yes.p", + "display": "13 Segmental" + }, + { + "code": "4.1.yes.q", + "display": "14 Subsegmental" + }, + { + "code": "4.1.yes.r", + "display": "Other Nodes (axilla, sub-diaphragmatic)" + } + ], + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "4.1.yes" + }, + { + "url": "#answer", + "valueCoding": { + "code": "4.1.yes.a" + } + } + ] + } + ], + "linkId": "4.1.yes.description", + "text": "Description of node:", + "type": "string" + } + ] + } + ] + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "4.2" + } + ], + "linkId": "4.2", + "text": "Other notable findings:", + "type": "text" + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "5" + } + ], + "linkId": "5.0", + "text": "M Category (Suspicious Extrathoracic Findings (M1b))", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "5.1" + } + ], + "linkId": "5.1", + "text": "Are there suspicious extrathoracic findings?", + "type": "boolean", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory", + "valueCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/ValueSet/questionnaire-display-category", + "code": "instructions" + } + ] + } + } + ], + "text": "*If yes, indicate and describe the structures below which apply. If no, go to 6.", + "type": "display" + }, + { + "linkId": "g5.1.yes", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "5.1" + }, + { + "url": "#answer", + "valueBoolean": true + } + ] + } + ], + "linkId": "5.1.yes", + "text": "Applicable Structures and Descriptions:", + "type": "choice", + "repeats": true, + "option": [ + { + "code": "5.1.yes.a", + "display": "Adrenals" + }, + { + "code": "5.1.yes.b", + "display": "Liver" + }, + { + "code": "5.1.yes.c", + "display": "Bone" + }, + { + "code": "5.1.yes.d", + "display": "Other" + } + ], + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "5.1.yes" + }, + { + "url": "#answer", + "valueCoding": { + "code": "5.1.yes.d" + } + } + ] + } + ], + "linkId": "5.1.yes.d.description", + "text": "Description of structures:", + "type": "string" + } + ] + } + ] + } + ] + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "6" + } + ], + "linkId": "6.0", + "text": "Additional Findings", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "6.1" + } + ], + "linkId": "6.1", + "text": "Are there additional findings?", + "type": "boolean", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory", + "valueCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/ValueSet/questionnaire-display-category", + "code": "instructions" + } + ] + } + } + ], + "text": "*If yes, indicate and describe the findings below which apply. If no, go to Impressions.", + "type": "display" + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "6.2" + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "6.1" + }, + { + "url": "#answer", + "valueBoolean": true + } + ] + } + ], + "linkId": "6.2", + "text": "Findings and Descriptions:", + "type": "open-choice", + "repeats": true, + "option": [ + { + "code": "6.2a", + "display": "Emphysema" + }, + { + "code": "6.2b", + "display": "Fibrosis" + }, + { + "code": "6.2c", + "display": "Coronary artery classification" + }, + { + "code": "6.2d", + "display": "Asbestos related pleural disease" + }, + { + "code": "6.2e", + "display": "Interstitial lung disease" + }, + { + "code": "6.2f", + "display": "Atherosclerosis" + }, + { + "code": "6.2g", + "display": "Pulmonary Embolism" + } + ], + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "6.2" + }, + { + "url": "#answer", + "valueCoding": { + "code": "6.2a" + } + } + ] + } + ], + "linkId": "6.2a", + "text": "Pulmonary, description:", + "type": "string" + } + ] + } + ] + } + ] + }, + { + "linkId": "g7", + "text": "IMPRESSIONS", + "type": "group", + "item": [ + { + "linkId": "g7.1", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "7.1" + } + ], + "linkId": "7.1", + "text": "Impression/Summary:", + "type": "text" + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "7.2" + } + ], + "linkId": "g7.2", + "text": "Radiologic Staging (TNM Version – 7th edition)", + "type": "group", + "item": [ + { + "linkId": "g7.20", + "text": "If this is a biopsy proven carcinoma, the preliminary radiologic stage is:", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "i)" + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-questionControl", + "valueCodeableConcept": { + "coding": [ + { + "code": "radio-button", + "display": "Radio Button" + } + ] + } + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-choiceOrientation", + "valueCode": "horizontal" + } + ], + "linkId": "7.2i", + "text": "Primary Tumour (T):", + "type": "choice", + "option": [ + { + "code": "T1a", + "display": "T1a" + }, + { + "code": "T1b", + "display": "T1b" + }, + { + "code": "T2", + "display": "T2" + }, + { + "code": "T2a", + "display": "T2a" + }, + { + "code": "T2b", + "display": "T2b" + }, + { + "code": "T3", + "display": "T3" + }, + { + "code": "T4", + "display": "T4" + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "ii)" + } + ], + "linkId": "7.2ii", + "text": "Regional Lymph Nodes (N):", + "type": "choice", + "option": [ + { + "code": "NX", + "display": "NX" + }, + { + "code": "N0", + "display": "N0" + }, + { + "code": "N1", + "display": "N1" + }, + { + "code": "N2", + "display": "N2" + }, + { + "code": "N3", + "display": "N3" + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "iii)" + } + ], + "linkId": "7.2iii", + "text": "Distant Metastasis (M):", + "type": "choice", + "option": [ + { + "code": "M0", + "display": "M0" + }, + { + "code": "M1", + "display": "M1" + }, + { + "code": "M1a", + "display": "M1a" + }, + { + "code": "M1b", + "display": "M1b" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] +} From ee414b73d9755ebfa9ba40714b8c837930bd971d Mon Sep 17 00:00:00 2001 From: Martha Mitran Date: Tue, 28 Nov 2023 13:12:05 -0800 Subject: [PATCH 02/14] Pass properties through to Remote Terminology Service on CodeSystem lookup (#5477) * Pass properties through to Remote Terminology Service on CodeSystem operation * Propagate list of property names throughout. Introduce a parameter object for the lookupCode method. Mark other lookupCode methods as deprecated. Add unit tests. * Update remote terminology service tests * Address code review comments * Fix unit tests * Address latest code review comments to update default methods * Address latest code review comments to update default methods - update fallback condition * Address latest code review comments to update default methods - update fallback condition --- .../context/support/IValidationSupport.java | 57 +++- .../context/support/LookupCodeRequest.java | 56 ++++ .../uhn/hapi/fhir/docs/ValidatorExamples.java | 5 +- ...e-terminology-service-for-code-system.yaml | 5 + .../jpa/dao/JpaResourceDaoCodeSystem.java | 41 ++- .../BaseJpaResourceProviderCodeSystem.java | 11 +- .../provider/ValueSetOperationProvider.java | 42 +-- .../ValueSetOperationProviderDstu2.java | 13 +- .../ca/uhn/fhir/jpa/term/TermReadSvcImpl.java | 25 +- ...ProviderDstu3CodeSystemPropertiesTest.java | 101 ++++++ .../ResourceProviderDstu3ValueSetTest.java | 14 +- ...rceProviderR4CodeSystemPropertiesTest.java | 91 ++++++ .../r4/ResourceProviderR4CodeSystemTest.java | 23 ++ ...rceProviderR4ValueSetNoVerCSNoVerTest.java | 8 +- .../jpa/term/TerminologySvcDeltaR4Test.java | 15 +- ...erminologySvcImplCurrentVersionR4Test.java | 13 +- ...rceProviderR5CodeSystemPropertiesTest.java | 89 ++++++ .../r5/ResourceProviderR5ValueSetTest.java | 8 +- .../CodeSystemLookupWithPropertiesUtil.java | 28 ++ ...rminologyDisplayPopulationInterceptor.java | 5 +- .../api/dao/IFhirResourceDaoCodeSystem.java | 10 + .../support/BaseValidationSupportWrapper.java | 8 +- .../support/CachingValidationSupport.java | 15 +- .../CommonCodeSystemsTerminologyService.java | 36 ++- ...oryTerminologyServerValidationSupport.java | 21 +- ...teTerminologyServiceValidationSupport.java | 33 +- ...ownCodeSystemWarningValidationSupport.java | 8 +- .../support/ValidationSupportChain.java | 27 +- ...ologyDisplayPopulationInterceptorTest.java | 10 - ...mmonCodeSystemsTerminologyServiceTest.java | 21 +- ...rminologyServiceValidationSupportTest.java | 297 +++++++----------- ...logyServiceValidationSupportDstu3Test.java | 189 +++++------ .../FhirInstanceValidatorR4Test.java | 8 +- .../FhirInstanceValidatorR4BTest.java | 35 ++- ...inologyServiceValidationSupportR5Test.java | 14 +- 35 files changed, 890 insertions(+), 492 deletions(-) create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/LookupCodeRequest.java create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5476-pass-properties-through-remote-terminology-service-for-code-system.yaml create mode 100644 hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3CodeSystemPropertiesTest.java create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemPropertiesTest.java create mode 100644 hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5CodeSystemPropertiesTest.java create mode 100644 hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/provider/CodeSystemLookupWithPropertiesUtil.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java index 27e0f5ddea5..3f9c4030c44 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java @@ -329,14 +329,16 @@ public interface IValidationSupport { } /** - * Look up a code using the system and code value + * Look up a code using the system and code value. + * @deprecated This method has been deprecated in HAPI FHIR 7.0.0. Use {@link IValidationSupport#lookupCode(ValidationSupportContext, LookupCodeRequest)} instead. * * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. * @param theSystem The CodeSystem URL * @param theCode The code - * @param theDisplayLanguage to filter out the designation by the display language. To return all designation, set this value to null. + * @param theDisplayLanguage Used to filter out the designation by the display language. To return all designation, set this value to null. */ + @Deprecated @Nullable default LookupCodeResult lookupCode( ValidationSupportContext theValidationSupportContext, @@ -348,12 +350,14 @@ public interface IValidationSupport { /** * Look up a code using the system and code value + * @deprecated This method has been deprecated in HAPI FHIR 7.0.0. Use {@link IValidationSupport#lookupCode(ValidationSupportContext, LookupCodeRequest)} instead. * * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. * @param theSystem The CodeSystem URL * @param theCode The code */ + @Deprecated @Nullable default LookupCodeResult lookupCode( ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { @@ -361,7 +365,26 @@ public interface IValidationSupport { } /** - * Returns true if the given valueset can be validated by the given + * Look up a code using the system, code and other parameters captured in {@link LookupCodeRequest}. + * @since 7.0.0 + * + * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to + * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. + * @param theLookupCodeRequest The parameters used to perform the lookup, including system and code. + */ + @Nullable + default LookupCodeResult lookupCode( + ValidationSupportContext theValidationSupportContext, @Nonnull LookupCodeRequest theLookupCodeRequest) { + // TODO: can change to return null once the deprecated methods are removed + return lookupCode( + theValidationSupportContext, + theLookupCodeRequest.getSystem(), + theLookupCodeRequest.getCode(), + theLookupCodeRequest.getDisplayLanguage()); + } + + /** + * Returns true if the given ValueSet can be validated by the given * validation support module * * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to @@ -554,6 +577,11 @@ public interface IValidationSupport { } class CodeValidationResult { + public static final String SOURCE_DETAILS = "sourceDetails"; + public static final String RESULT = "result"; + public static final String MESSAGE = "message"; + public static final String DISPLAY = "display"; + private String myCode; private String myMessage; private IssueSeverity mySeverity; @@ -682,6 +710,23 @@ public interface IValidationSupport { setSeverity(IssueSeverity.valueOf(theIssueSeverity.toUpperCase())); return this; } + + public IBaseParameters toParameters(FhirContext theContext) { + IBaseParameters retVal = ParametersUtil.newInstance(theContext); + + ParametersUtil.addParameterToParametersBoolean(theContext, retVal, RESULT, isOk()); + if (isNotBlank(getMessage())) { + ParametersUtil.addParameterToParametersString(theContext, retVal, MESSAGE, getMessage()); + } + if (isNotBlank(getDisplay())) { + ParametersUtil.addParameterToParametersString(theContext, retVal, DISPLAY, getDisplay()); + } + if (isNotBlank(getSourceDetails())) { + ParametersUtil.addParameterToParametersString(theContext, retVal, SOURCE_DETAILS, getSourceDetails()); + } + + return retVal; + } } class ValueSetExpansionOutcome { @@ -814,7 +859,7 @@ public interface IValidationSupport { } public IBaseParameters toParameters( - FhirContext theContext, List> theProperties) { + FhirContext theContext, List> thePropertyNames) { IBaseParameters retVal = ParametersUtil.newInstance(theContext); if (isNotBlank(getCodeSystemDisplayName())) { @@ -829,8 +874,8 @@ public interface IValidationSupport { if (myProperties != null) { Set properties = Collections.emptySet(); - if (theProperties != null) { - properties = theProperties.stream() + if (thePropertyNames != null) { + properties = thePropertyNames.stream() .map(IPrimitiveType::getValueAsString) .collect(Collectors.toSet()); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/LookupCodeRequest.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/LookupCodeRequest.java new file mode 100644 index 00000000000..dc7074bba25 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/LookupCodeRequest.java @@ -0,0 +1,56 @@ +package ca.uhn.fhir.context.support; + +import java.util.Collection; +import java.util.Collections; + +/** + * Represents parameters which can be passed to the $lookup operation for codes. + * @since 7.0.0 + */ +public class LookupCodeRequest { + private final String mySystem; + private final String myCode; + private String myDisplayLanguage; + private Collection myPropertyNames; + + /** + * @param theSystem The CodeSystem URL + * @param theCode The code + */ + public LookupCodeRequest(String theSystem, String theCode) { + mySystem = theSystem; + myCode = theCode; + } + + /** + * @param theSystem The CodeSystem URL + * @param theCode The code + * @param theDisplayLanguage Used to filter out the designation by the display language. To return all designation, set this value to null. + * @param thePropertyNames The collection of properties to be returned in the output. If no properties are specified, the implementor chooses what to return. + */ + public LookupCodeRequest( + String theSystem, String theCode, String theDisplayLanguage, Collection thePropertyNames) { + this(theSystem, theCode); + myDisplayLanguage = theDisplayLanguage; + myPropertyNames = thePropertyNames; + } + + public String getSystem() { + return mySystem; + } + + public String getCode() { + return myCode; + } + + public String getDisplayLanguage() { + return myDisplayLanguage; + } + + public Collection getPropertyNames() { + if (myPropertyNames == null) { + return Collections.emptyList(); + } + return myPropertyNames; + } +} diff --git a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ValidatorExamples.java b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ValidatorExamples.java index ed304aa3b48..d75bd9d5064 100644 --- a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ValidatorExamples.java +++ b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ValidatorExamples.java @@ -23,6 +23,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.parser.IParser; @@ -308,9 +309,7 @@ public class ValidatorExamples { @Override public LookupCodeResult lookupCode( ValidationSupportContext theValidationSupportContext, - String theSystem, - String theCode, - String theDisplayLanguage) { + @Nonnull LookupCodeRequest validationSupportParameterObject) { // TODO: implement (or return null if your implementation does not support this function) return null; } diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5476-pass-properties-through-remote-terminology-service-for-code-system.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5476-pass-properties-through-remote-terminology-service-for-code-system.yaml new file mode 100644 index 00000000000..f4b02e23951 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5476-pass-properties-through-remote-terminology-service-for-code-system.yaml @@ -0,0 +1,5 @@ +--- +type: add +issue: 5476 +title: "A new method on the IValidationSupport interface called lookupCode(LookupCodeRequest) has been added. +This method will replace the existing lookupCode methods, which are now deprecated." diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoCodeSystem.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoCodeSystem.java index e4b128f608e..12a4f01a947 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoCodeSystem.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoCodeSystem.java @@ -24,6 +24,7 @@ import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult; +import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; @@ -41,6 +42,7 @@ import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.FhirTerser; import ca.uhn.hapi.converters.canonical.VersionCanonicalizer; +import org.apache.commons.collections4.CollectionUtils; import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService; import org.hl7.fhir.instance.model.api.IBaseCoding; import org.hl7.fhir.instance.model.api.IBaseDatatype; @@ -52,8 +54,10 @@ import org.hl7.fhir.r4.model.Coding; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; import java.util.List; +import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.PostConstruct; @@ -125,8 +129,33 @@ public class JpaResourceDaoCodeSystem extends BaseHapiF IBaseCoding theCoding, IPrimitiveType theDisplayLanguage, RequestDetails theRequestDetails) { + return lookupCode( + theCode, + theSystem, + theCoding, + theDisplayLanguage, + CollectionUtils.emptyCollection(), + theRequestDetails); + } + + @Nonnull + @Override + public IValidationSupport.LookupCodeResult lookupCode( + IPrimitiveType theCode, + IPrimitiveType theSystem, + IBaseCoding theCoding, + IPrimitiveType theDisplayLanguage, + Collection> thePropertyNames, + RequestDetails theRequestDetails) { return doLookupCode( - myFhirContext, myTerser, myValidationSupport, theCode, theSystem, theCoding, theDisplayLanguage); + myFhirContext, + myTerser, + myValidationSupport, + theCode, + theSystem, + theCoding, + theDisplayLanguage, + thePropertyNames); } @Override @@ -285,7 +314,8 @@ public class JpaResourceDaoCodeSystem extends BaseHapiF IPrimitiveType theCode, IPrimitiveType theSystem, IBaseCoding theCoding, - IPrimitiveType theDisplayLanguage) { + IPrimitiveType theDisplayLanguage, + Collection> thePropertyNames) { boolean haveCoding = theCoding != null && isNotBlank(extractCodingSystem(theCoding)) && isNotBlank(extractCodingCode(theCoding)); @@ -323,11 +353,16 @@ public class JpaResourceDaoCodeSystem extends BaseHapiF ourLog.info("Looking up {} / {}", system, code); + Collection propertyNames = CollectionUtils.emptyIfNull(thePropertyNames).stream() + .map(IPrimitiveType::getValueAsString) + .collect(Collectors.toSet()); + if (theValidationSupport.isCodeSystemSupported(new ValidationSupportContext(theValidationSupport), system)) { ourLog.info("Code system {} is supported", system); IValidationSupport.LookupCodeResult retVal = theValidationSupport.lookupCode( - new ValidationSupportContext(theValidationSupport), system, code, displayLanguage); + new ValidationSupportContext(theValidationSupport), + new LookupCodeRequest(system, code, displayLanguage, propertyNames)); if (retVal != null) { return retVal; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderCodeSystem.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderCodeSystem.java index 1929635de94..85b88f5f0bc 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderCodeSystem.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderCodeSystem.java @@ -45,7 +45,6 @@ import java.util.Optional; import java.util.function.Supplier; import javax.servlet.http.HttpServletRequest; -import static ca.uhn.fhir.jpa.provider.ValueSetOperationProvider.toValidateCodeResult; import static org.apache.commons.lang3.StringUtils.isNotBlank; public abstract class BaseJpaResourceProviderCodeSystem extends BaseJpaResourceProvider { @@ -65,6 +64,7 @@ public abstract class BaseJpaResourceProviderCodeSystem @OperationParam(name = "version", typeName = "string", min = 0), @OperationParam(name = "display", typeName = "string", min = 1), @OperationParam(name = "abstract", typeName = "boolean", min = 1), + @OperationParam(name = "property", min = 0, max = OperationParam.MAX_UNLIMITED, typeName = "code") }) public IBaseParameters lookup( HttpServletRequest theServletRequest, @@ -75,7 +75,7 @@ public abstract class BaseJpaResourceProviderCodeSystem @OperationParam(name = "displayLanguage", min = 0, max = 1, typeName = "code") IPrimitiveType theDisplayLanguage, @OperationParam(name = "property", min = 0, max = OperationParam.MAX_UNLIMITED, typeName = "code") - List> theProperties, + List> thePropertyNames, RequestDetails theRequestDetails) { startRequest(theServletRequest); @@ -83,9 +83,10 @@ public abstract class BaseJpaResourceProviderCodeSystem IFhirResourceDaoCodeSystem dao = (IFhirResourceDaoCodeSystem) getDao(); IValidationSupport.LookupCodeResult result; applyVersionToSystem(theSystem, theVersion); - result = dao.lookupCode(theCode, theSystem, theCoding, theDisplayLanguage, theRequestDetails); + result = dao.lookupCode( + theCode, theSystem, theCoding, theDisplayLanguage, thePropertyNames, theRequestDetails); result.throwNotFoundIfAppropriate(); - return result.toParameters(theRequestDetails.getFhirContext(), theProperties); + return result.toParameters(theRequestDetails.getFhirContext(), thePropertyNames); } finally { endRequest(theServletRequest); } @@ -191,7 +192,7 @@ public abstract class BaseJpaResourceProviderCodeSystem theCodeableConcept, theRequestDetails); } - return toValidateCodeResult(getContext(), result); + return result.toParameters(getContext()); } finally { endRequest(theServletRequest); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ValueSetOperationProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ValueSetOperationProvider.java index 99189c92154..2bb327cf9c7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ValueSetOperationProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ValueSetOperationProvider.java @@ -19,7 +19,6 @@ */ package ca.uhn.fhir.jpa.provider; -import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult; @@ -61,10 +60,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; public class ValueSetOperationProvider extends BaseJpaProvider { private static final Logger ourLog = LoggerFactory.getLogger(ValueSetOperationProvider.class); - public static final String SOURCE_DETAILS = "sourceDetails"; - public static final String RESULT = "result"; - public static final String MESSAGE = "message"; - public static final String DISPLAY = "display"; @Autowired protected IValidationSupport myValidationSupport; @@ -149,10 +144,10 @@ public class ValueSetOperationProvider extends BaseJpaProvider { idempotent = true, typeName = "ValueSet", returnParameters = { - @OperationParam(name = RESULT, typeName = "boolean", min = 1), - @OperationParam(name = MESSAGE, typeName = "string"), - @OperationParam(name = DISPLAY, typeName = "string"), - @OperationParam(name = SOURCE_DETAILS, typeName = "string") + @OperationParam(name = CodeValidationResult.RESULT, typeName = "boolean", min = 1), + @OperationParam(name = CodeValidationResult.MESSAGE, typeName = "string"), + @OperationParam(name = CodeValidationResult.DISPLAY, typeName = "string"), + @OperationParam(name = CodeValidationResult.SOURCE_DETAILS, typeName = "string") }) public IBaseParameters validateCode( HttpServletRequest theServletRequest, @@ -164,7 +159,8 @@ public class ValueSetOperationProvider extends BaseJpaProvider { @OperationParam(name = "system", min = 0, max = 1, typeName = "uri") IPrimitiveType theSystem, @OperationParam(name = "systemVersion", min = 0, max = 1, typeName = "string") IPrimitiveType theSystemVersion, - @OperationParam(name = DISPLAY, min = 0, max = 1, typeName = "string") IPrimitiveType theDisplay, + @OperationParam(name = CodeValidationResult.DISPLAY, min = 0, max = 1, typeName = "string") + IPrimitiveType theDisplay, @OperationParam(name = "coding", min = 0, max = 1, typeName = "Coding") IBaseCoding theCoding, @OperationParam(name = "codeableConcept", min = 0, max = 1, typeName = "CodeableConcept") ICompositeType theCodeableConcept, @@ -228,7 +224,7 @@ public class ValueSetOperationProvider extends BaseJpaProvider { theCodeableConcept, theRequestDetails); } - return toValidateCodeResult(getContext(), result); + return result.toParameters(getContext()); } finally { endRequest(theServletRequest); } @@ -256,7 +252,9 @@ public class ValueSetOperationProvider extends BaseJpaProvider { name = ProviderConstants.OPERATION_INVALIDATE_EXPANSION, idempotent = false, typeName = "ValueSet", - returnParameters = {@OperationParam(name = MESSAGE, typeName = "string", min = 1, max = 1)}) + returnParameters = { + @OperationParam(name = CodeValidationResult.MESSAGE, typeName = "string", min = 1, max = 1) + }) public IBaseParameters invalidateValueSetExpansion( @IdParam IIdType theValueSetId, RequestDetails theRequestDetails, HttpServletRequest theServletRequest) { startRequest(theServletRequest); @@ -265,7 +263,7 @@ public class ValueSetOperationProvider extends BaseJpaProvider { String outcome = myTermReadSvc.invalidatePreCalculatedExpansion(theValueSetId, theRequestDetails); IBaseParameters retVal = ParametersUtil.newInstance(getContext()); - ParametersUtil.addParameterToParametersString(getContext(), retVal, MESSAGE, outcome); + ParametersUtil.addParameterToParametersString(getContext(), retVal, CodeValidationResult.MESSAGE, outcome); return retVal; } finally { @@ -326,22 +324,4 @@ public class ValueSetOperationProvider extends BaseJpaProvider { return options; } - - public static IBaseParameters toValidateCodeResult(FhirContext theContext, CodeValidationResult theResult) { - IBaseParameters retVal = ParametersUtil.newInstance(theContext); - - ParametersUtil.addParameterToParametersBoolean(theContext, retVal, RESULT, theResult.isOk()); - if (isNotBlank(theResult.getMessage())) { - ParametersUtil.addParameterToParametersString(theContext, retVal, MESSAGE, theResult.getMessage()); - } - if (isNotBlank(theResult.getDisplay())) { - ParametersUtil.addParameterToParametersString(theContext, retVal, DISPLAY, theResult.getDisplay()); - } - if (isNotBlank(theResult.getSourceDetails())) { - ParametersUtil.addParameterToParametersString( - theContext, retVal, SOURCE_DETAILS, theResult.getSourceDetails()); - } - - return retVal; - } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ValueSetOperationProviderDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ValueSetOperationProviderDstu2.java index 372b030629b..0480f0290d2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ValueSetOperationProviderDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/ValueSetOperationProviderDstu2.java @@ -127,7 +127,7 @@ public class ValueSetOperationProviderDstu2 extends ValueSetOperationProvider { @OperationParam(name = "displayLanguage", min = 0, max = 1, typeName = "code") IPrimitiveType theDisplayLanguage, @OperationParam(name = "property", min = 0, max = OperationParam.MAX_UNLIMITED, typeName = "code") - List> theProperties, + List> thePropertyNames, RequestDetails theRequestDetails) { startRequest(theServletRequest); @@ -137,9 +137,16 @@ public class ValueSetOperationProviderDstu2 extends ValueSetOperationProvider { FhirTerser terser = getContext().newTerser(); result = JpaResourceDaoCodeSystem.doLookupCode( - getContext(), terser, myValidationSupport, theCode, theSystem, theCoding, theDisplayLanguage); + getContext(), + terser, + myValidationSupport, + theCode, + theSystem, + theCoding, + theDisplayLanguage, + thePropertyNames); result.throwNotFoundIfAppropriate(); - return result.toParameters(theRequestDetails.getFhirContext(), theProperties); + return result.toParameters(theRequestDetails.getFhirContext(), thePropertyNames); } finally { endRequest(theServletRequest); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java index 73f95569ba6..9372b9b03b9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java @@ -23,6 +23,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.i18n.Msg; @@ -90,6 +91,7 @@ import ca.uhn.hapi.converters.canonical.VersionCanonicalizer; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Stopwatch; import com.google.common.collect.ArrayListMultimap; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; @@ -147,7 +149,6 @@ import org.springframework.transaction.interceptor.NoRollbackRuleAttribute; import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.transaction.support.TransactionTemplate; -import org.springframework.util.CollectionUtils; import org.springframework.util.comparator.Comparators; import java.util.ArrayList; @@ -2575,10 +2576,13 @@ public class TermReadSvcImpl implements ITermReadSvc, IHasScheduledJobs { return new IFhirResourceDaoCodeSystem.SubsumesResult(subsumes); } - protected IValidationSupport.LookupCodeResult lookupCode( - String theSystem, String theCode, String theDisplayLanguage) { + @Override + public IValidationSupport.LookupCodeResult lookupCode( + ValidationSupportContext theValidationSupportContext, @Nonnull LookupCodeRequest theLookupCodeRequest) { TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); return txTemplate.execute(t -> { + final String theSystem = theLookupCodeRequest.getSystem(); + final String theCode = theLookupCodeRequest.getCode(); Optional codeOpt = findCode(theSystem, theCode); if (codeOpt.isPresent()) { TermConcept code = codeOpt.get(); @@ -2593,7 +2597,7 @@ public class TermReadSvcImpl implements ITermReadSvc, IHasScheduledJobs { for (TermConceptDesignation next : code.getDesignations()) { // filter out the designation based on displayLanguage if any - if (isDisplayLanguageMatch(theDisplayLanguage, next.getLanguage())) { + if (isDisplayLanguageMatch(theLookupCodeRequest.getDisplayLanguage(), next.getLanguage())) { IValidationSupport.ConceptDesignation designation = new IValidationSupport.ConceptDesignation(); designation.setLanguage(next.getLanguage()); designation.setUseSystem(next.getUseSystem()); @@ -2604,7 +2608,11 @@ public class TermReadSvcImpl implements ITermReadSvc, IHasScheduledJobs { } } + final Collection propertyNames = theLookupCodeRequest.getPropertyNames(); for (TermConceptProperty next : code.getProperties()) { + if (ObjectUtils.isNotEmpty(propertyNames) && !propertyNames.contains(next.getKey())) { + continue; + } if (next.getType() == TermConceptPropertyTypeEnum.CODING) { IValidationSupport.CodingConceptProperty property = new IValidationSupport.CodingConceptProperty( @@ -3117,15 +3125,6 @@ public class TermReadSvcImpl implements ITermReadSvc, IHasScheduledJobs { return isValueSetPreExpandedForCodeValidation(valueSetR4); } - @Override - public LookupCodeResult lookupCode( - ValidationSupportContext theValidationSupportContext, - String theSystem, - String theCode, - String theDisplayLanguage) { - return lookupCode(theSystem, theCode, theDisplayLanguage); - } - private static class TermCodeSystemVersionDetails { private final long myPid; diff --git a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3CodeSystemPropertiesTest.java b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3CodeSystemPropertiesTest.java new file mode 100644 index 00000000000..66eaa679850 --- /dev/null +++ b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3CodeSystemPropertiesTest.java @@ -0,0 +1,101 @@ +package ca.uhn.fhir.jpa.provider.dstu3; + +import ca.uhn.fhir.jpa.model.util.JpaConstants; +import ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil; +import ca.uhn.fhir.rest.gclient.IOperationUntypedWithInputAndPartialOutput; +import org.hl7.fhir.dstu3.model.CodeSystem; +import org.hl7.fhir.dstu3.model.CodeType; +import org.hl7.fhir.dstu3.model.Parameters; +import org.hl7.fhir.dstu3.model.StringType; +import org.hl7.fhir.dstu3.model.UriType; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Iterator; +import java.util.List; +import java.util.stream.Stream; + +import static ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil.ourCode; +import static ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil.ourCodeSystemId; +import static ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil.ourCodeSystemUrl; +import static ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil.ourPropertyA; +import static ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil.ourPropertyB; +import static ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil.ourPropertyValueA; +import static ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil.ourPropertyValueB; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ResourceProviderDstu3CodeSystemPropertiesTest extends BaseResourceProviderDstu3Test { + + public static Stream parametersLookup() { + return CodeSystemLookupWithPropertiesUtil.parametersLookupWithProperties(); + } + + @ParameterizedTest + @MethodSource(value = "parametersLookup") + public void testLookup_withProperties_returnsCorrectParameters(List theLookupProperties, List theExpectedReturnedProperties) { + // setup + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setId(ourCodeSystemId); + codeSystem.setUrl(ourCodeSystemUrl); + CodeSystem.ConceptDefinitionComponent concept = codeSystem.addConcept().setCode(ourCode); + CodeSystem.ConceptPropertyComponent propertyComponent = new CodeSystem.ConceptPropertyComponent() + .setCode(ourPropertyA).setValue(new StringType(ourPropertyValueA)); + concept.addProperty(propertyComponent); + propertyComponent = new CodeSystem.ConceptPropertyComponent() + .setCode(ourPropertyB).setValue(new StringType(ourPropertyValueB)); + concept.addProperty(propertyComponent); + myCodeSystemDao.create(codeSystem, mySrd); + + // test + IOperationUntypedWithInputAndPartialOutput respParam = myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_LOOKUP) + .withParameter(Parameters.class, "code", new CodeType(ourCode)) + .andParameter("system", new UriType(ourCodeSystemUrl)); + + theLookupProperties.forEach(p -> respParam.andParameter("property", new CodeType(p))); + Parameters parameters = respParam.execute(); + + Iterator paramIterator = parameters.getParameter().iterator(); + Parameters.ParametersParameterComponent parameter = null; + while (paramIterator.hasNext()) { + Parameters.ParametersParameterComponent currentParameter = paramIterator.next(); + if (currentParameter.getName().equals("property")) { + parameter = currentParameter; + break; + } + } + + if (theExpectedReturnedProperties.isEmpty()) { + assertNull(parameter); + return; + } + + Iterator propertyIterator = concept.getProperty().stream() + .filter(property -> theExpectedReturnedProperties.contains(property.getCode())).iterator(); + + while (propertyIterator.hasNext()) { + CodeSystem.ConceptPropertyComponent property = propertyIterator.next(); + assertNotNull(parameter); + + Iterator parameterPartIterator = parameter.getPart().iterator(); + + parameter = parameterPartIterator.next(); + assertEquals("code", parameter.getName()); + assertEquals(property.getCode(), ((CodeType)parameter.getValue()).getValue()); + + parameter = parameterPartIterator.next(); + assertEquals("value", parameter.getName()); + assertTrue(property.getValue().equalsShallow(parameter.getValue())); + + if (paramIterator.hasNext()) { + parameter = paramIterator.next(); + } + } + } +} diff --git a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java index 09ce4621a13..5bee62a6211 100644 --- a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java +++ b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.provider.dstu3; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; @@ -9,7 +10,6 @@ import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; import ca.uhn.fhir.jpa.model.dao.JpaPid; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.provider.ValueSetOperationProvider; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -790,13 +790,13 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); ourLog.info(resp); - assertEquals(ValueSetOperationProvider.RESULT, respParam.getParameter().get(0).getName()); + assertEquals(IValidationSupport.CodeValidationResult.RESULT, respParam.getParameter().get(0).getName()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); - assertEquals(ValueSetOperationProvider.DISPLAY, respParam.getParameter().get(1).getName()); + assertEquals(IValidationSupport.CodeValidationResult.DISPLAY, respParam.getParameter().get(1).getName()); assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); - assertEquals(ValueSetOperationProvider.SOURCE_DETAILS, respParam.getParameter().get(2).getName()); + assertEquals(IValidationSupport.CodeValidationResult.SOURCE_DETAILS, respParam.getParameter().get(2).getName()); assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/administrative-gender", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); } @@ -820,13 +820,13 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3 String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); ourLog.info(resp); - assertEquals(ValueSetOperationProvider.RESULT, respParam.getParameter().get(0).getName()); + assertEquals(IValidationSupport.CodeValidationResult.RESULT, respParam.getParameter().get(0).getName()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); - assertEquals(ValueSetOperationProvider.DISPLAY, respParam.getParameter().get(1).getName()); + assertEquals(IValidationSupport.CodeValidationResult.DISPLAY, respParam.getParameter().get(1).getName()); assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); - assertEquals(ValueSetOperationProvider.SOURCE_DETAILS, respParam.getParameter().get(2).getName()); + assertEquals(IValidationSupport.CodeValidationResult.SOURCE_DETAILS, respParam.getParameter().get(2).getName()); assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/administrative-gender", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemPropertiesTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemPropertiesTest.java new file mode 100644 index 00000000000..7338a0b4174 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemPropertiesTest.java @@ -0,0 +1,91 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import ca.uhn.fhir.jpa.model.util.JpaConstants; +import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test; +import ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil; +import ca.uhn.fhir.rest.gclient.IOperationUntypedWithInputAndPartialOutput; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.UriType; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Iterator; +import java.util.List; +import java.util.stream.Stream; + +import static ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil.ourCode; +import static ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil.ourCodeSystemId; +import static ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil.ourCodeSystemUrl; +import static ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil.ourPropertyA; +import static ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil.ourPropertyB; +import static ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil.ourPropertyValueA; +import static ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil.ourPropertyValueB; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ResourceProviderR4CodeSystemPropertiesTest extends BaseResourceProviderR4Test { + public static Stream parametersLookup() { + return CodeSystemLookupWithPropertiesUtil.parametersLookupWithProperties(); + } + + @ParameterizedTest + @MethodSource(value = "parametersLookup") + public void testLookup_withProperties_returnsCorrectParameters(List theLookupProperties, List theExpectedReturnedProperties) { + // setup + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setId(ourCodeSystemId); + codeSystem.setUrl(ourCodeSystemUrl); + CodeSystem.ConceptDefinitionComponent concept = codeSystem.addConcept().setCode(ourCode); + CodeSystem.ConceptPropertyComponent propertyComponent = new CodeSystem.ConceptPropertyComponent() + .setCode(ourPropertyA).setValue(new StringType(ourPropertyValueA)); + concept.addProperty(propertyComponent); + propertyComponent = new CodeSystem.ConceptPropertyComponent() + .setCode(ourPropertyB).setValue(new StringType(ourPropertyValueB)); + concept.addProperty(propertyComponent); + myCodeSystemDao.create(codeSystem, mySrd); + + // test + IOperationUntypedWithInputAndPartialOutput respParam = myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_LOOKUP) + .withParameter(Parameters.class, "code", new CodeType(ourCode)) + .andParameter("system", new UriType(ourCodeSystemUrl)); + + theLookupProperties.forEach(p -> respParam.andParameter("property", new CodeType(p))); + Parameters parameters = respParam.execute(); + + + if (theExpectedReturnedProperties.isEmpty()) { + assertFalse(parameters.hasParameter("property")); + return; + } + + assertTrue(parameters.hasParameter("property")); + Iterator parameterPropertyIterator = parameters.getParameters("property").iterator(); + + Iterator propertyIterator = concept.getProperty().stream() + .filter(property -> theExpectedReturnedProperties.contains(property.getCode())).iterator(); + + while (propertyIterator.hasNext()) { + CodeSystem.ConceptPropertyComponent property = propertyIterator.next(); + + assertTrue(parameterPropertyIterator.hasNext()); + Parameters.ParametersParameterComponent parameter = parameterPropertyIterator.next(); + Iterator parameterPartIterator = parameter.getPart().iterator(); + + parameter = parameterPartIterator.next(); + assertEquals("code", parameter.getName()); + assertEquals(property.getCode(), ((CodeType)parameter.getValue()).getCode()); + + parameter = parameterPartIterator.next(); + assertEquals("value", parameter.getName()); + assertTrue(property.getValue().equalsShallow(parameter.getValue())); + } + } +} diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemTest.java index 24958f4153b..06693388bee 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemTest.java @@ -138,6 +138,29 @@ public class ResourceProviderR4CodeSystemTest extends BaseResourceProviderR4Test assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue()); } + @Test + public void testLookupOperationByCodeAndSystemWithPropertiesBuiltInCode() { + Parameters respParam = myClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("ACSN")) + .andParameter("system", new UriType("http://terminology.hl7.org/CodeSystem/v2-0203")) + .execute(); + + String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("name", respParam.getParameter().get(0).getName()); + assertEquals("v2.0203", ((StringType) respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("version", respParam.getParameter().get(1).getName()); + assertEquals("2.9", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(2).getName()); + assertEquals("Accession ID", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); + assertEquals("abstract", respParam.getParameter().get(3).getName()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue()); + } + @Test public void testLookupOperationByCodeAndSystemBuiltInNonexistantCode() { try { diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetNoVerCSNoVerTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetNoVerCSNoVerTest.java index 3ac4d4ed784..7b69cc6a741 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetNoVerCSNoVerTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetNoVerCSNoVerTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.provider.r4; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; @@ -14,7 +15,6 @@ import ca.uhn.fhir.jpa.model.dao.JpaPid; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test; -import ca.uhn.fhir.jpa.provider.ValueSetOperationProvider; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; @@ -1295,13 +1295,13 @@ public class ResourceProviderR4ValueSetNoVerCSNoVerTest extends BaseResourceProv String resp = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); ourLog.info(resp); - assertEquals(ValueSetOperationProvider.RESULT, respParam.getParameter().get(0).getName()); + assertEquals(IValidationSupport.CodeValidationResult.RESULT, respParam.getParameter().get(0).getName()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); - assertEquals(ValueSetOperationProvider.DISPLAY, respParam.getParameter().get(1).getName()); + assertEquals(IValidationSupport.CodeValidationResult.DISPLAY, respParam.getParameter().get(1).getName()); assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); - assertEquals(ValueSetOperationProvider.SOURCE_DETAILS, respParam.getParameter().get(2).getName()); + assertEquals(IValidationSupport.CodeValidationResult.SOURCE_DETAILS, respParam.getParameter().get(2).getName()); assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/administrative-gender", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcDeltaR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcDeltaR4Test.java index dd0fe20bf61..7ab96037c26 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcDeltaR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcDeltaR4Test.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.ValidationSupportContext; +import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; import ca.uhn.fhir.jpa.entity.TermConcept; @@ -369,7 +370,7 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test { assertEquals("http://foo", outcome.getUrl()); assertEquals(CodeSystem.CodeSystemContentMode.NOTPRESENT, outcome.getContent()); - IValidationSupport.LookupCodeResult lookup = myTermSvc.lookupCode(new ValidationSupportContext(myValidationSupport), "http://foo", "CBC", null); + IValidationSupport.LookupCodeResult lookup = myTermSvc.lookupCode(new ValidationSupportContext(myValidationSupport), new LookupCodeRequest("http://foo", "CBC")); assertEquals("Complete Blood Count", lookup.getCodeDisplay()); } @@ -433,7 +434,8 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test { UploadStatistics outcome = myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo", delta); assertEquals(2, outcome.getUpdatedConceptCount()); - assertEquals("CODEA0", myTermSvc.lookupCode(new ValidationSupportContext(myValidationSupport), "http://foo", "codea", null).getCodeDisplay()); + assertEquals("CODEA0", myTermSvc.lookupCode(new ValidationSupportContext(myValidationSupport), + new LookupCodeRequest("http://foo", "codea")).getCodeDisplay()); // Add codes again with different display delta = new CustomTerminologySet(); @@ -441,12 +443,14 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test { delta.addRootConcept("codeb", "CODEB1"); outcome = myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo", delta); assertEquals(2, outcome.getUpdatedConceptCount()); - assertEquals("CODEA1", myTermSvc.lookupCode(new ValidationSupportContext(myValidationSupport), "http://foo", "codea", null).getCodeDisplay()); + assertEquals("CODEA1", myTermSvc.lookupCode(new ValidationSupportContext(myValidationSupport), + new LookupCodeRequest("http://foo", "codea")).getCodeDisplay()); // Add codes again with no changes outcome = myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://foo", delta); assertEquals(2, outcome.getUpdatedConceptCount()); - assertEquals("CODEA1", myTermSvc.lookupCode(new ValidationSupportContext(myValidationSupport), "http://foo", "codea", null).getCodeDisplay()); + assertEquals("CODEA1", myTermSvc.lookupCode(new ValidationSupportContext(myValidationSupport), + new LookupCodeRequest("http://foo", "codea")).getCodeDisplay()); } @Test @@ -481,7 +485,8 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test { .setValue(new Coding("http://snomed.info", "1234567", "Choked on large meal (finding)")); myCodeSystemDao.create(cs, mySrd); - IValidationSupport.LookupCodeResult result = myTermSvc.lookupCode(new ValidationSupportContext(myValidationSupport), "http://foo/cs", "lunch", null); + IValidationSupport.LookupCodeResult result = myTermSvc.lookupCode(new ValidationSupportContext(myValidationSupport), + new LookupCodeRequest("http://foo/cs", "lunch")); assertEquals(true, result.isFound()); assertEquals("lunch", result.getSearchedForCode()); assertEquals("http://foo/cs", result.getSearchedForSystem()); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplCurrentVersionR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplCurrentVersionR4Test.java index 387609117bf..17cdf197835 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplCurrentVersionR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplCurrentVersionR4Test.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.ValidationSupportContext; +import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.config.JpaConfig; import ca.uhn.fhir.jpa.entity.TermCodeSystem; @@ -214,13 +215,13 @@ public class TerminologySvcImplCurrentVersionR4Test extends BaseJpaR4Test { private void validateValueLookup(String theCurrentVersion, Collection allVersions) { IValidationSupport.LookupCodeResult resultNoVer = myValidationSupport.lookupCode( - new ValidationSupportContext(myValidationSupport), BASE_LOINC_URL, VS_NO_VERSIONED_ON_UPLOAD_FIRST_CODE, null); + new ValidationSupportContext(myValidationSupport), new LookupCodeRequest(BASE_LOINC_URL, VS_NO_VERSIONED_ON_UPLOAD_FIRST_CODE)); assertNotNull(resultNoVer); String expectedNoVer = prefixWithVersion(theCurrentVersion, VS_NO_VERSIONED_ON_UPLOAD_FIRST_DISPLAY); assertEquals(expectedNoVer, resultNoVer.getCodeDisplay()); IValidationSupport.LookupCodeResult resultWithVer = myValidationSupport.lookupCode( - new ValidationSupportContext(myValidationSupport), BASE_LOINC_URL, VS_VERSIONED_ON_UPLOAD_FIRST_CODE, null); + new ValidationSupportContext(myValidationSupport), new LookupCodeRequest(BASE_LOINC_URL, VS_VERSIONED_ON_UPLOAD_FIRST_CODE)); assertNotNull(resultWithVer); String expectedWithVer = prefixWithVersion(theCurrentVersion, VS_VERSIONED_ON_UPLOAD_FIRST_DISPLAY); assertEquals(expectedWithVer, resultWithVer.getCodeDisplay()); @@ -231,15 +232,15 @@ public class TerminologySvcImplCurrentVersionR4Test extends BaseJpaR4Test { private void lookupForVersion(String theVersion) { IValidationSupport.LookupCodeResult resultNoVer = myValidationSupport.lookupCode( - new ValidationSupportContext(myValidationSupport), BASE_LOINC_URL + "|" + theVersion, - VS_NO_VERSIONED_ON_UPLOAD_FIRST_CODE, null); + new ValidationSupportContext(myValidationSupport), + new LookupCodeRequest(BASE_LOINC_URL + "|" + theVersion, VS_NO_VERSIONED_ON_UPLOAD_FIRST_CODE)); assertNotNull(resultNoVer); String expectedNoVer = prefixWithVersion(theVersion, VS_NO_VERSIONED_ON_UPLOAD_FIRST_DISPLAY); assertEquals(expectedNoVer, resultNoVer.getCodeDisplay()); IValidationSupport.LookupCodeResult resultWithVer = myValidationSupport.lookupCode( - new ValidationSupportContext(myValidationSupport), BASE_LOINC_URL + "|" + theVersion, - VS_VERSIONED_ON_UPLOAD_FIRST_CODE, null); + new ValidationSupportContext(myValidationSupport), + new LookupCodeRequest(BASE_LOINC_URL + "|" + theVersion, VS_VERSIONED_ON_UPLOAD_FIRST_CODE)); assertNotNull(resultWithVer); String expectedWithVer = prefixWithVersion(theVersion, VS_VERSIONED_ON_UPLOAD_FIRST_DISPLAY); assertEquals(expectedWithVer, resultWithVer.getCodeDisplay()); diff --git a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5CodeSystemPropertiesTest.java b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5CodeSystemPropertiesTest.java new file mode 100644 index 00000000000..e8b5be13fb4 --- /dev/null +++ b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5CodeSystemPropertiesTest.java @@ -0,0 +1,89 @@ +package ca.uhn.fhir.jpa.provider.r5; + +import ca.uhn.fhir.jpa.model.util.JpaConstants; +import ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil; +import ca.uhn.fhir.rest.gclient.IOperationUntypedWithInputAndPartialOutput; +import org.hl7.fhir.r5.model.CodeSystem; +import org.hl7.fhir.r5.model.CodeType; +import org.hl7.fhir.r5.model.Parameters; +import org.hl7.fhir.r5.model.StringType; +import org.hl7.fhir.r5.model.UriType; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Iterator; +import java.util.List; +import java.util.stream.Stream; + +import static ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil.ourCode; +import static ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil.ourCodeSystemId; +import static ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil.ourCodeSystemUrl; +import static ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil.ourPropertyA; +import static ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil.ourPropertyB; +import static ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil.ourPropertyValueA; +import static ca.uhn.fhir.jpa.provider.CodeSystemLookupWithPropertiesUtil.ourPropertyValueB; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ResourceProviderR5CodeSystemPropertiesTest extends BaseResourceProviderR5Test { + public static Stream parametersLookup() { + return CodeSystemLookupWithPropertiesUtil.parametersLookupWithProperties(); + } + @ParameterizedTest + @MethodSource(value = "parametersLookup") + public void testLookup_withProperties_returnsCorrectParameters(List theLookupProperties, List theExpectedReturnedProperties) { + // setup + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setId(ourCodeSystemId); + codeSystem.setUrl(ourCodeSystemUrl); + CodeSystem.ConceptDefinitionComponent concept = codeSystem.addConcept().setCode(ourCode); + CodeSystem.ConceptPropertyComponent propertyComponent = new CodeSystem.ConceptPropertyComponent() + .setCode(ourPropertyA).setValue(new StringType(ourPropertyValueA)); + concept.addProperty(propertyComponent); + propertyComponent = new CodeSystem.ConceptPropertyComponent() + .setCode(ourPropertyB).setValue(new StringType(ourPropertyValueB)); + concept.addProperty(propertyComponent); + myCodeSystemDao.create(codeSystem, mySrd); + + // test + IOperationUntypedWithInputAndPartialOutput respParam = myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_LOOKUP) + .withParameter(Parameters.class, "code", new CodeType(ourCode)) + .andParameter("system", new UriType(ourCodeSystemUrl)); + + theLookupProperties.forEach(p -> respParam.andParameter("property", new CodeType(p))); + Parameters parameters = respParam.execute(); + + + if (theExpectedReturnedProperties.isEmpty()) { + assertFalse(parameters.hasParameter("property")); + return; + } + + assertTrue(parameters.hasParameter("property")); + Iterator parameterPropertyIterator = parameters.getParameters("property").iterator(); + + Iterator propertyIterator = concept.getProperty().stream() + .filter(property -> theExpectedReturnedProperties.contains(property.getCode())).iterator(); + + while (propertyIterator.hasNext()) { + CodeSystem.ConceptPropertyComponent property = propertyIterator.next(); + + assertTrue(parameterPropertyIterator.hasNext()); + Parameters.ParametersParameterComponent parameter = parameterPropertyIterator.next(); + Iterator parameterPartIterator = parameter.getPart().iterator(); + + parameter = parameterPartIterator.next(); + assertEquals("code", parameter.getName()); + assertEquals(property.getCode(), ((CodeType)parameter.getValue()).getCode()); + + parameter = parameterPartIterator.next(); + assertEquals("value", parameter.getName()); + assertTrue(property.getValue().equalsShallow(parameter.getValue())); + } + } +} diff --git a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java index 6d9b8a299c7..a40eddf4e05 100644 --- a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java +++ b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.provider.r5; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; @@ -12,7 +13,6 @@ import ca.uhn.fhir.jpa.entity.TermValueSetConcept; import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum; import ca.uhn.fhir.jpa.model.dao.JpaPid; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.provider.ValueSetOperationProvider; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -1229,13 +1229,13 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test { String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); ourLog.info(resp); - assertEquals(ValueSetOperationProvider.RESULT, respParam.getParameter().get(0).getName()); + assertEquals(IValidationSupport.CodeValidationResult.RESULT, respParam.getParameter().get(0).getName()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); - assertEquals(ValueSetOperationProvider.DISPLAY, respParam.getParameter().get(1).getName()); + assertEquals(IValidationSupport.CodeValidationResult.DISPLAY, respParam.getParameter().get(1).getName()); assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); - assertEquals(ValueSetOperationProvider.SOURCE_DETAILS, respParam.getParameter().get(2).getName()); + assertEquals(IValidationSupport.CodeValidationResult.SOURCE_DETAILS, respParam.getParameter().get(2).getName()); assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/administrative-gender", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); } diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/provider/CodeSystemLookupWithPropertiesUtil.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/provider/CodeSystemLookupWithPropertiesUtil.java new file mode 100644 index 00000000000..feedbd05f0e --- /dev/null +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/provider/CodeSystemLookupWithPropertiesUtil.java @@ -0,0 +1,28 @@ +package ca.uhn.fhir.jpa.provider; + +import org.junit.jupiter.params.provider.Arguments; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.params.provider.Arguments.arguments; + +public class CodeSystemLookupWithPropertiesUtil { + public static final String ourCodeSystemId = "CodeSystem-Example", + ourCodeSystemUrl = "http://example/" + ourCodeSystemId; + public static final String ourCode = "Code-WithProperties"; + public static final String ourPropertyA = "Property-A", ourPropertyB = "Property-B"; + public static final String ourPropertyValueA = "Value-A", ourPropertyValueB = "Value-B"; + + public static Stream parametersLookupWithProperties() { + return Stream.of( + arguments(List.of(ourPropertyB), List.of(ourPropertyB)), + arguments(List.of(ourPropertyA, ourPropertyB), List.of(ourPropertyA, ourPropertyB)), + arguments(List.of(ourPropertyB, ourPropertyA), List.of(ourPropertyB, ourPropertyA)), + arguments(List.of(ourPropertyA, ourPropertyA), List.of(ourPropertyA, ourPropertyA)), + arguments(List.of(ourPropertyB, "ABC"), List.of(ourPropertyB)), + arguments(List.of("ABC", ourPropertyA), List.of(ourPropertyA)), + arguments(List.of("ABC"), Collections.emptyList())); + } +} diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseTerminologyDisplayPopulationInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseTerminologyDisplayPopulationInterceptor.java index c6a7c901ee6..e86facffe09 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseTerminologyDisplayPopulationInterceptor.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseTerminologyDisplayPopulationInterceptor.java @@ -24,6 +24,7 @@ import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; import ca.uhn.fhir.context.BaseRuntimeElementDefinition; import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition; import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.interceptor.api.Hook; import ca.uhn.fhir.interceptor.api.Pointcut; @@ -122,8 +123,8 @@ public class ResponseTerminologyDisplayPopulationInterceptor extends BaseRespons ValidationSupportContext validationSupportContext = new ValidationSupportContext(myValidationSupport); if (myValidationSupport.isCodeSystemSupported(validationSupportContext, system)) { - IValidationSupport.LookupCodeResult lookupCodeResult = - myValidationSupport.lookupCode(validationSupportContext, system, code); + IValidationSupport.LookupCodeResult lookupCodeResult = myValidationSupport.lookupCode( + validationSupportContext, new LookupCodeRequest(system, code)); if (lookupCodeResult != null && lookupCodeResult.isFound()) { String newDisplay = lookupCodeResult.getCodeDisplay(); IPrimitiveType newString = myStringDefinition.newInstance(newDisplay); diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoCodeSystem.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoCodeSystem.java index bf323ac8226..3ac4d312cd1 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoCodeSystem.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoCodeSystem.java @@ -32,6 +32,7 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.codesystems.ConceptSubsumptionOutcome; import org.springframework.transaction.annotation.Transactional; +import java.util.Collection; import java.util.List; import javax.annotation.Nonnull; @@ -55,6 +56,15 @@ public interface IFhirResourceDaoCodeSystem extends IFh IPrimitiveType theDisplayLanguage, RequestDetails theRequestDetails); + @Nonnull + IValidationSupport.LookupCodeResult lookupCode( + IPrimitiveType theCode, + IPrimitiveType theSystem, + IBaseCoding theCoding, + IPrimitiveType theDisplayLanguage, + Collection> thePropertyNames, + RequestDetails theRequestDetails); + SubsumesResult subsumes( IPrimitiveType theCodeA, IPrimitiveType theCodeB, diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/BaseValidationSupportWrapper.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/BaseValidationSupportWrapper.java index e14a686e208..ecd220a3b98 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/BaseValidationSupportWrapper.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/BaseValidationSupportWrapper.java @@ -3,6 +3,7 @@ package org.hl7.fhir.common.hapi.validation.support; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.context.support.TranslateConceptResults; import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValueSetExpansionOptions; @@ -98,11 +99,8 @@ public abstract class BaseValidationSupportWrapper extends BaseValidationSupport @Override public LookupCodeResult lookupCode( - ValidationSupportContext theValidationSupportContext, - String theSystem, - String theCode, - String theDisplayLanguage) { - return myWrap.lookupCode(theValidationSupportContext, theSystem, theCode, theDisplayLanguage); + ValidationSupportContext theValidationSupportContext, @Nonnull LookupCodeRequest theLookupCodeRequest) { + return myWrap.lookupCode(theValidationSupportContext, theLookupCodeRequest); } @Override diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java index 873a854f1f3..82d86f234fb 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java @@ -3,6 +3,7 @@ package org.hl7.fhir.common.hapi.validation.support; import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.context.support.TranslateConceptResults; import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValueSetExpansionOptions; @@ -195,15 +196,13 @@ public class CachingValidationSupport extends BaseValidationSupportWrapper imple @Override public LookupCodeResult lookupCode( - ValidationSupportContext theValidationSupportContext, - String theSystem, - String theCode, - String theDisplayLanguage) { - String key = "lookupCode " + theSystem + " " + theCode + " " + defaultIfBlank(theDisplayLanguage, "NO_LANG"); + ValidationSupportContext theValidationSupportContext, @Nonnull LookupCodeRequest theLookupCodeRequest) { + String key = "lookupCode " + theLookupCodeRequest.getSystem() + " " + + theLookupCodeRequest.getCode() + + " " + defaultIfBlank(theLookupCodeRequest.getDisplayLanguage(), "NO_LANG") + + " " + theLookupCodeRequest.getPropertyNames().toString(); return loadFromCache( - myLookupCodeCache, - key, - t -> super.lookupCode(theValidationSupportContext, theSystem, theCode, theDisplayLanguage)); + myLookupCodeCache, key, t -> super.lookupCode(theValidationSupportContext, theLookupCodeRequest)); } @Override diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java index af9cd737566..f334cbcf29b 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java @@ -5,6 +5,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.util.ClasspathUtil; @@ -221,7 +222,8 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport { @Nullable public CodeValidationResult validateLookupCode( ValidationSupportContext theValidationSupportContext, String theCode, String theSystem) { - LookupCodeResult lookupResult = lookupCode(theValidationSupportContext, theSystem, theCode); + LookupCodeResult lookupResult = + lookupCode(theValidationSupportContext, new LookupCodeRequest(theSystem, theCode)); CodeValidationResult validationResult = null; if (lookupResult != null) { if (lookupResult.isFound()) { @@ -240,18 +242,18 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport { @Override public LookupCodeResult lookupCode( - ValidationSupportContext theValidationSupportContext, - String theSystem, - String theCode, - String theDisplayLanguage) { + ValidationSupportContext theValidationSupportContext, @Nonnull LookupCodeRequest theLookupCodeRequest) { + final String code = theLookupCodeRequest.getCode(); + final String system = theLookupCodeRequest.getSystem(); + Map map; - switch (theSystem) { + switch (system) { case LANGUAGES_CODESYSTEM_URL: - return lookupLanguageCode(theCode); + return lookupLanguageCode(code); case UCUM_CODESYSTEM_URL: - return lookupUcumCode(theCode); + return lookupUcumCode(code); case MIMETYPES_CODESYSTEM_URL: - return lookupMimetypeCode(theCode); + return lookupMimetypeCode(code); case COUNTRIES_CODESYSTEM_URL: map = ISO_3166_CODES; break; @@ -265,11 +267,11 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport { return null; } - String display = map.get(theCode); + String display = map.get(code); if (isNotBlank(display)) { LookupCodeResult retVal = new LookupCodeResult(); - retVal.setSearchedForCode(theCode); - retVal.setSearchedForSystem(theSystem); + retVal.setSearchedForCode(code); + retVal.setSearchedForSystem(system); retVal.setFound(true); retVal.setCodeDisplay(display); return retVal; @@ -277,10 +279,10 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport { // If we get here it means we know the codesystem but the code was bad LookupCodeResult retVal = new LookupCodeResult(); - retVal.setSearchedForCode(theCode); - retVal.setSearchedForSystem(theSystem); + retVal.setSearchedForCode(code); + retVal.setSearchedForSystem(system); retVal.setFound(false); - retVal.setErrorMessage("Code '" + theCode + "' is not valid for system: " + theSystem); + retVal.setErrorMessage("Code '" + code + "' is not valid for system: " + system); return retVal; } @@ -384,7 +386,7 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport { String language = theNext.get("Subtag").asText(); ArrayNode descriptions = (ArrayNode) theNext.get("Description"); String description = null; - if (descriptions.size() > 0) { + if (!descriptions.isEmpty()) { description = descriptions.get(0).asText(); } theLanguagesMap.put(language, description); @@ -403,7 +405,7 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport { @Nonnull private LookupCodeResult lookupUcumCode(String theCode) { InputStream input = ClasspathUtil.loadResourceAsStream("/ucum-essence.xml"); - String outcome = null; + String outcome; LookupCodeResult retVal = new LookupCodeResult(); retVal.setSearchedForCode(theCode); retVal.setSearchedForSystem(UCUM_CODESYSTEM_URL); diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java index a2a709c0be6..2fa1c80befc 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.i18n.Msg; @@ -611,16 +612,20 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu @Override public LookupCodeResult lookupCode( - ValidationSupportContext theValidationSupportContext, - String theSystem, - String theCode, - String theDisplayLanguage) { + ValidationSupportContext theValidationSupportContext, @Nonnull LookupCodeRequest theLookupCodeRequest) { + final String code = theLookupCodeRequest.getCode(); + final String system = theLookupCodeRequest.getSystem(); CodeValidationResult codeValidationResult = validateCode( - theValidationSupportContext, new ConceptValidationOptions(), theSystem, theCode, null, null); + theValidationSupportContext, + new ConceptValidationOptions(), + system, + code, + theLookupCodeRequest.getDisplayLanguage(), + null); if (codeValidationResult == null) { return null; } - return codeValidationResult.asLookupCodeResult(theSystem, theCode); + return codeValidationResult.asLookupCodeResult(system, code); } @Nullable @@ -927,9 +932,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu .getRootValidationSupport() .lookupCode( theValidationSupportContext, - includeOrExcludeConceptSystemUrl, - theWantCode, - null); + new LookupCodeRequest(includeOrExcludeConceptSystemUrl, theWantCode)); if (lookup != null) { ableToHandleCode = true; if (lookup.isFound()) { diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java index a889d36f05e..3bd5d2db48b 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java @@ -5,6 +5,7 @@ import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.context.support.TranslateConceptResults; import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.i18n.Msg; @@ -183,11 +184,11 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup @Override public LookupCodeResult lookupCode( - ValidationSupportContext theValidationSupportContext, - String theSystem, - String theCode, - String theDisplayLanguage) { - Validate.notBlank(theCode, "theCode must be provided"); + ValidationSupportContext theValidationSupportContext, @Nonnull LookupCodeRequest theLookupCodeRequest) { + final String code = theLookupCodeRequest.getCode(); + final String system = theLookupCodeRequest.getSystem(); + final String displayLanguage = theLookupCodeRequest.getDisplayLanguage(); + Validate.notBlank(code, "theCode must be provided"); IGenericClient client = provideClient(); FhirContext fhirContext = client.getFhirContext(); @@ -197,17 +198,20 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup case DSTU3: case R4: IBaseParameters params = ParametersUtil.newInstance(fhirContext); - ParametersUtil.addParameterToParametersString(fhirContext, params, "code", theCode); - if (!StringUtils.isEmpty(theSystem)) { - ParametersUtil.addParameterToParametersString(fhirContext, params, "system", theSystem); + ParametersUtil.addParameterToParametersString(fhirContext, params, "code", code); + if (!StringUtils.isEmpty(system)) { + ParametersUtil.addParameterToParametersString(fhirContext, params, "system", system); } - if (!StringUtils.isEmpty(theDisplayLanguage)) { - ParametersUtil.addParameterToParametersString(fhirContext, params, "language", theDisplayLanguage); + if (!StringUtils.isEmpty(displayLanguage)) { + ParametersUtil.addParameterToParametersString(fhirContext, params, "language", displayLanguage); } - Class codeSystemClass = + for (String propertyName : theLookupCodeRequest.getPropertyNames()) { + ParametersUtil.addParameterToParametersString(fhirContext, params, "property", propertyName); + } + Class codeSystemClass = myCtx.getResourceDefinition("CodeSystem").getImplementingClass(); IBaseParameters outcome = client.operation() - .onType((Class) codeSystemClass) + .onType(codeSystemClass) .named("$lookup") .withParameters(params) .useHttpGet() @@ -216,10 +220,9 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup switch (fhirVersion) { case DSTU3: return generateLookupCodeResultDSTU3( - theCode, theSystem, (org.hl7.fhir.dstu3.model.Parameters) outcome); + code, system, (org.hl7.fhir.dstu3.model.Parameters) outcome); case R4: - return generateLookupCodeResultR4( - theCode, theSystem, (org.hl7.fhir.r4.model.Parameters) outcome); + return generateLookupCodeResultR4(code, system, (org.hl7.fhir.r4.model.Parameters) outcome); } } break; diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.java index 0b4be8d9006..f8493bbaf03 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.java @@ -2,6 +2,7 @@ package org.hl7.fhir.common.hapi.validation.support; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.ConceptValidationOptions; +import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.context.support.ValidationSupportContext; import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -51,12 +52,9 @@ public class UnknownCodeSystemWarningValidationSupport extends BaseValidationSup @Nullable @Override public LookupCodeResult lookupCode( - ValidationSupportContext theValidationSupportContext, - String theSystem, - String theCode, - String theDisplayLanguage) { + ValidationSupportContext theValidationSupportContext, @Nonnull LookupCodeRequest theLookupCodeRequest) { // filters out error/fatal - if (canValidateCodeSystem(theValidationSupportContext, theSystem)) { + if (canValidateCodeSystem(theValidationSupportContext, theLookupCodeRequest.getSystem())) { return new LookupCodeResult().setFound(true); } diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.java index 2b31d6852d3..65b6245fb29 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.java @@ -5,6 +5,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.context.support.TranslateConceptResults; import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValueSetExpansionOptions; @@ -416,20 +417,26 @@ public class ValidationSupportChain implements IValidationSupport { @Override public LookupCodeResult lookupCode( - ValidationSupportContext theValidationSupportContext, - String theSystem, - String theCode, - String theDisplayLanguage) { + ValidationSupportContext theValidationSupportContext, @Nonnull LookupCodeRequest theLookupCodeRequest) { for (IValidationSupport next : myChain) { - if (next.isCodeSystemSupported(theValidationSupportContext, theSystem)) { - LookupCodeResult lookupCodeResult = - next.lookupCode(theValidationSupportContext, theSystem, theCode, theDisplayLanguage); + final String system = theLookupCodeRequest.getSystem(); + final String code = theLookupCodeRequest.getCode(); + final String displayLanguage = theLookupCodeRequest.getDisplayLanguage(); + if (next.isCodeSystemSupported(theValidationSupportContext, system)) { + LookupCodeResult lookupCodeResult = next.lookupCode(theValidationSupportContext, theLookupCodeRequest); + if (lookupCodeResult == null) { + /* + This branch has been added as a fall-back mechanism for supporting lookupCode + methods marked as deprecated in interface IValidationSupport. + */ + lookupCodeResult = next.lookupCode(theValidationSupportContext, system, code, displayLanguage); + } if (ourLog.isDebugEnabled()) { ourLog.debug( "Code {}|{}{} {} by {}", - theSystem, - theCode, - isBlank(theDisplayLanguage) ? "" : " (" + theDisplayLanguage + ")", + system, + code, + isBlank(displayLanguage) ? "" : " (" + theLookupCodeRequest.getDisplayLanguage() + ")", lookupCodeResult != null && lookupCodeResult.isFound() ? "found" : "not found", next.getName()); } diff --git a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseTerminologyDisplayPopulationInterceptorTest.java b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseTerminologyDisplayPopulationInterceptorTest.java index 2b476d65a31..99f5a4c00e6 100644 --- a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseTerminologyDisplayPopulationInterceptorTest.java +++ b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseTerminologyDisplayPopulationInterceptorTest.java @@ -138,16 +138,6 @@ public class ResponseTerminologyDisplayPopulationInterceptorTest { public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { return true; } - - @Override - public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theDisplayLanguage) { - return null; - } - - @Override - public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { - return lookupCode(theValidationSupportContext, theSystem, theCode, null); - } } } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyServiceTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyServiceTest.java index dc0d72961a0..cb8c812949c 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyServiceTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyServiceTest.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.context.FhirContext; 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.LookupCodeRequest; import ca.uhn.fhir.i18n.Msg; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.CodeSystem; @@ -31,35 +32,35 @@ public class CommonCodeSystemsTerminologyServiceTest { @Test public void testUcum_LookupCode_Good() { - IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(newSupport(), "http://unitsofmeasure.org", "Cel"); + IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(newSupport(), new LookupCodeRequest("http://unitsofmeasure.org", "Cel")); assert outcome != null; assertTrue(outcome.isFound()); } @Test public void testUcum_LookupCode_Good2() { - IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(newSupport(), "http://unitsofmeasure.org", "kg/m2"); + IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(newSupport(), new LookupCodeRequest("http://unitsofmeasure.org", "kg/m2")); assert outcome != null; assertTrue(outcome.isFound()); } @Test public void testUcum_LookupCode_Bad() { - IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(newSupport(), "http://unitsofmeasure.org", "AAAAA"); + IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(newSupport(), new LookupCodeRequest("http://unitsofmeasure.org", "AAAAA")); assert outcome != null; assertFalse(outcome.isFound()); } @Test public void testUcum_LookupCode_UnknownSystem() { - IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(newSupport(), "http://foo", "AAAAA", null); + IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(newSupport(), new LookupCodeRequest("http://foo", "AAAAA")); assertNull(outcome); } @Test public void lookupCode_languageOnlyLookup_isCaseInsensitive() { - IValidationSupport.LookupCodeResult outcomeUpper = mySvc.lookupCode(newSupport(), "urn:ietf:bcp:47", "SGN", "Sign Languages"); - IValidationSupport.LookupCodeResult outcomeLower = mySvc.lookupCode(newSupport(), "urn:ietf:bcp:47", "sgn", "Sign Languages"); + IValidationSupport.LookupCodeResult outcomeUpper = mySvc.lookupCode(newSupport(), new LookupCodeRequest("urn:ietf:bcp:47", "SGN", "Sign Languages", null)); + IValidationSupport.LookupCodeResult outcomeLower = mySvc.lookupCode(newSupport(), new LookupCodeRequest("urn:ietf:bcp:47", "sgn", "Sign Languages", null)); assertNotNull(outcomeUpper); assertNotNull(outcomeLower); assertTrue(outcomeLower.isFound()); @@ -68,8 +69,8 @@ public class CommonCodeSystemsTerminologyServiceTest { @Test public void lookupCode_languageAndRegionLookup_isCaseInsensitive() { - IValidationSupport.LookupCodeResult outcomeUpper = mySvc.lookupCode(newSupport(), "urn:ietf:bcp:47", "EN-US", "English"); - IValidationSupport.LookupCodeResult outcomeLower = mySvc.lookupCode(newSupport(), "urn:ietf:bcp:47", "en-us", "English"); + IValidationSupport.LookupCodeResult outcomeUpper = mySvc.lookupCode(newSupport(), new LookupCodeRequest("urn:ietf:bcp:47", "EN-US", "English", null)); + IValidationSupport.LookupCodeResult outcomeLower = mySvc.lookupCode(newSupport(), new LookupCodeRequest("urn:ietf:bcp:47", "en-us", "English", null)); assertNotNull(outcomeUpper); assertNotNull(outcomeLower); assertTrue(outcomeLower.isFound()); @@ -131,14 +132,14 @@ public class CommonCodeSystemsTerminologyServiceTest { @Test public void testLanguages_CommonLanguagesVs_OnlyLanguage_NoRegion() { - IValidationSupport.LookupCodeResult nl = mySvc.lookupCode(newSupport(), "urn:ietf:bcp:47", "nl"); + IValidationSupport.LookupCodeResult nl = mySvc.lookupCode(newSupport(), new LookupCodeRequest("urn:ietf:bcp:47", "nl")); assertTrue(nl.isFound()); assertEquals("Dutch", nl.getCodeDisplay()); } @Test public void testLanguages_CommonLanguagesVs_LanguageAndRegion() { - IValidationSupport.LookupCodeResult nl = mySvc.lookupCode(newSupport(), "urn:ietf:bcp:47", "nl-NL"); + IValidationSupport.LookupCodeResult nl = mySvc.lookupCode(newSupport(), new LookupCodeRequest("urn:ietf:bcp:47", "nl-NL")); assertTrue(nl.isFound()); assertEquals("Dutch Netherlands", nl.getCodeDisplay()); } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupportTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupportTest.java index 99e8b754c3a..ebbf218b0e8 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupportTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupportTest.java @@ -5,6 +5,7 @@ import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.TranslateConceptResult; import ca.uhn.fhir.context.support.TranslateConceptResults; +import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.parser.IJsonLikeParser; import ca.uhn.fhir.rest.annotation.IdParam; @@ -24,6 +25,7 @@ import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; import ca.uhn.fhir.util.ParametersUtil; import com.google.common.collect.Lists; import org.hl7.fhir.instance.model.api.IBaseCoding; +import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.BooleanType; import org.hl7.fhir.r4.model.CodeSystem; @@ -47,11 +49,14 @@ import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.List; +import java.util.Set; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.lessThan; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -60,8 +65,7 @@ public class RemoteTerminologyServiceValidationSupportTest { private static final String DISPLAY = "DISPLAY"; private static final String LANGUAGE = "en"; private static final String CODE_SYSTEM = "CODE_SYS"; - private static final String CODE_SYSTEM_VERSION = "2.1"; - private static final String CODE_SYSTEM_VERSION_AS_TEXT = "v2.1.12"; + private static final String CODE_SYSTEM_NAME = "Code System"; private static final String CODE = "CODE"; private static final String VALUE_SET_URL = "http://value.set/url"; private static final String TARGET_SYSTEM = "http://target.system/url"; @@ -77,10 +81,10 @@ public class RemoteTerminologyServiceValidationSupportTest { private static final String ERROR_MESSAGE = "This is an error message"; private static final String SUCCESS_MESSAGE = "This is a success message"; - private static FhirContext ourCtx = FhirContext.forR4Cached(); + private static final FhirContext ourCtx = FhirContext.forR4Cached(); @RegisterExtension - public RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(ourCtx); + public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); private MyValueSetProvider myValueSetProvider; private RemoteTerminologyServiceValidationSupport mySvc; @@ -90,15 +94,15 @@ public class RemoteTerminologyServiceValidationSupportTest { @BeforeEach public void before() { myValueSetProvider = new MyValueSetProvider(); - myRestfulServerExtension.getRestfulServer().registerProvider(myValueSetProvider); + ourRestfulServerExtension.getRestfulServer().registerProvider(myValueSetProvider); myCodeSystemProvider = new MyCodeSystemProvider(); - myRestfulServerExtension.getRestfulServer().registerProvider(myCodeSystemProvider); + ourRestfulServerExtension.getRestfulServer().registerProvider(myCodeSystemProvider); myConceptMapProvider = new MyConceptMapProvider(); - myRestfulServerExtension.getRestfulServer().registerProvider(myConceptMapProvider); + ourRestfulServerExtension.getRestfulServer().registerProvider(myConceptMapProvider); - String baseUrl = "http://localhost:" + myRestfulServerExtension.getPort(); + String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx); mySvc.setBaseUrl(baseUrl); @@ -111,69 +115,98 @@ public class RemoteTerminologyServiceValidationSupportTest { } @Test - public void testValidateCode_SystemCodeDisplayUrl_BlankCode() { + public void testValidateCode_withBlankCode_returnsNull() { IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, "", DISPLAY, VALUE_SET_URL); - assertEquals(null, outcome); + assertNull(outcome); } - @Test - public void testLookupOperation_CodeSystem_Success() { - createNextCodeSystemLookupReturnParameters(true, CODE_SYSTEM_VERSION, CODE_SYSTEM_VERSION_AS_TEXT, - DISPLAY, null); - IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, CODE_SYSTEM, CODE); + @Test + public void testLookupCode_forCodeSystemWithAllParams_returnsCorrectParameters() { + myCodeSystemProvider.myNextLookupCodeResult = new IValidationSupport.LookupCodeResult(); + myCodeSystemProvider.myNextLookupCodeResult.setFound(true); + myCodeSystemProvider.myNextLookupCodeResult.setCodeSystemVersion(CODE_SYSTEM); + myCodeSystemProvider.myNextLookupCodeResult.setSearchedForCode(CODE); + myCodeSystemProvider.myNextLookupCodeResult.setCodeSystemDisplayName(CODE_SYSTEM_NAME); + myCodeSystemProvider.myNextLookupCodeResult.setCodeDisplay(DISPLAY); + + // property + String propertyName = "birthDate"; + String propertyValue = "1930-01-01"; + IValidationSupport.BaseConceptProperty property = new IValidationSupport.StringConceptProperty(propertyName, propertyValue); + myCodeSystemProvider.myNextLookupCodeResult.getProperties().add(property); + + // designation + IValidationSupport.ConceptDesignation designation = new IValidationSupport.ConceptDesignation(); + designation.setLanguage("en"); + designation.setUseCode("code"); + designation.setUseSystem("system"); + designation.setUseDisplay("display"); + designation.setValue("some value"); + myCodeSystemProvider.myNextLookupCodeResult.getDesignations().add(designation); + + IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, new LookupCodeRequest(CODE_SYSTEM, CODE, null, Set.of("birthDate"))); assertNotNull(outcome, "Call to lookupCode() should return a non-NULL result!"); assertEquals(DISPLAY, outcome.getCodeDisplay()); - assertEquals(CODE_SYSTEM_VERSION, outcome.getCodeSystemVersion()); + assertEquals(CODE_SYSTEM, outcome.getCodeSystemVersion()); + assertEquals(CODE_SYSTEM_NAME, myCodeSystemProvider.myNextReturnParams.getParameterValue("name").toString()); assertEquals(CODE, myCodeSystemProvider.myLastCode.getCode()); assertEquals(CODE_SYSTEM, myCodeSystemProvider.myLastUrl.getValueAsString()); - assertEquals(CODE_SYSTEM_VERSION_AS_TEXT, myCodeSystemProvider.myNextReturnParams.getParameterValue("name").toString()); - assertTrue(Boolean.parseBoolean(myCodeSystemProvider.myNextReturnParams.getParameterValue("result").primitiveValue())); + + Parameters.ParametersParameterComponent propertyComponent = myCodeSystemProvider.myNextReturnParams.getParameter("property"); + assertNotNull(propertyComponent); + + Iterator propertyComponentIterator = propertyComponent.getPart().iterator(); + propertyComponent = propertyComponentIterator.next(); + assertEquals("code", propertyComponent.getName()); + assertEquals(propertyName, ((StringType)propertyComponent.getValue()).getValue()); + + propertyComponent = propertyComponentIterator.next(); + assertEquals("value", propertyComponent.getName()); + assertEquals(propertyValue, ((StringType)propertyComponent.getValue()).getValue()); + + Parameters.ParametersParameterComponent designationComponent = myCodeSystemProvider.myNextReturnParams.getParameter("designation"); + Iterator partParameter = designationComponent.getPart().iterator(); + designationComponent = partParameter.next(); + assertEquals("language", designationComponent.getName()); + assertEquals(LANGUAGE, designationComponent.getValue().toString()); + + designationComponent = partParameter.next(); + assertEquals("use", designationComponent.getName()); + Coding coding = (Coding)designationComponent.getValue(); + assertNotNull(coding, "Coding value returned via designation use should NOT be NULL!"); + assertEquals("code", coding.getCode()); + assertEquals("system", coding.getSystem()); + assertEquals("display", coding.getDisplay()); + + designationComponent = partParameter.next(); + assertEquals("value", designationComponent.getName()); + assertEquals("some value", designationComponent.getValue().toString()); } @Test - public void testLookupOperationWithAllParams_CodeSystem_Success() { - createNextCodeSystemLookupReturnParameters(true, CODE_SYSTEM_VERSION, CODE_SYSTEM_VERSION_AS_TEXT, - DISPLAY, null); - addAdditionalReturnParameters(); - - IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, CODE_SYSTEM, CODE); - assertNotNull(outcome, "Call to lookupCode() should return a non-NULL result!"); - assertEquals(DISPLAY, outcome.getCodeDisplay()); - assertEquals(CODE_SYSTEM_VERSION, outcome.getCodeSystemVersion()); - assertEquals(CODE_SYSTEM_VERSION_AS_TEXT, myCodeSystemProvider.myNextReturnParams.getParameterValue("name").toString()); - - assertEquals(CODE, myCodeSystemProvider.myLastCode.getCode()); - assertEquals(CODE_SYSTEM, myCodeSystemProvider.myLastUrl.getValueAsString()); - assertTrue(Boolean.parseBoolean(myCodeSystemProvider.myNextReturnParams.getParameterValue("result").primitiveValue())); - - validateExtraCodeSystemParams(); + public void testLookupCode_forCodeSystemWithBlankCode_throwsException() { + Assertions.assertThrows(IllegalArgumentException.class, + () -> mySvc.lookupCode(null, new LookupCodeRequest(CODE_SYSTEM, ""))); } @Test - public void testLookupCode_BlankCode_ThrowsException() { - Assertions.assertThrows(IllegalArgumentException.class, () -> { - IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, CODE_SYSTEM, - "", null); - }); - } - - @Test - public void testValidateCode_ValueSet_Success() { + public void testValidateCode_forValueSet_returnsCorrectly() { createNextValueSetReturnParameters(true, DISPLAY, null); IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, VALUE_SET_URL); + assertNotNull(outcome); assertEquals(CODE, outcome.getCode()); assertEquals(DISPLAY, outcome.getDisplay()); - assertEquals(null, outcome.getSeverity()); - assertEquals(null, outcome.getMessage()); + assertNull(outcome.getSeverity()); + assertNull(outcome.getMessage()); assertEquals(CODE, myValueSetProvider.myLastCode.getCode()); assertEquals(DISPLAY, myValueSetProvider.myLastDisplay.getValue()); assertEquals(CODE_SYSTEM, myValueSetProvider.myLastSystem.getValue()); assertEquals(VALUE_SET_URL, myValueSetProvider.myLastUrl.getValue()); - assertEquals(null, myValueSetProvider.myLastValueSet); + assertNull(myValueSetProvider.myLastValueSet); } @Test @@ -182,75 +215,20 @@ public class RemoteTerminologyServiceValidationSupportTest { myValueSetProvider.myNextReturnValueSets = new ArrayList<>(); // when - IBaseResource valueSet = mySvc.fetchValueSet(VALUE_SET_URL); + mySvc.fetchValueSet(VALUE_SET_URL); // then assertEquals(SummaryEnum.FALSE, myValueSetProvider.myLastSummaryParam); } @Test - public void testValidateCodeWithAllParams_CodeSystem_Success() { - createNextCodeSystemReturnParameters(true, DISPLAY, null); - addAdditionalReturnParameters(); - - IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, null); - assertEquals(CODE, outcome.getCode()); - assertEquals(DISPLAY, outcome.getDisplay()); - assertEquals(null, outcome.getSeverity()); - assertEquals(null, outcome.getMessage()); - - validateExtraCodeSystemParams(); - } - - private void validateExtraCodeSystemParams() { - assertEquals(CODE, myCodeSystemProvider.myLastCode.getCode()); - assertEquals(CODE_SYSTEM, myCodeSystemProvider.myLastUrl.getValueAsString()); - for (Parameters.ParametersParameterComponent param : myCodeSystemProvider.myNextReturnParams.getParameter()) { - String paramName = param.getName(); - if (paramName.equals("result")) { - assertEquals(true, ((BooleanType)param.getValue()).booleanValue()); - } else if (paramName.equals("display")) { - assertEquals(DISPLAY, param.getValue().toString()); - } else if (paramName.equals("property")) { - for (Parameters.ParametersParameterComponent propertyComponent : param.getPart()) { - switch(propertyComponent.getName()) { - case "name": - assertEquals("birthDate", propertyComponent.getValue().toString()); - break; - case "value": - assertEquals("1930-01-01", propertyComponent.getValue().toString()); - break; - } - } - } else if (paramName.equals("designation")) { - for (Parameters.ParametersParameterComponent designationComponent : param.getPart()) { - switch(designationComponent.getName()) { - case "language": - assertEquals(LANGUAGE, designationComponent.getValue().toString()); - break; - case "use": - Coding coding = (Coding)designationComponent.getValue(); - assertNotNull(coding, "Coding value returned via designation use should NOT be NULL!"); - assertEquals("code", coding.getCode()); - assertEquals("system", coding.getSystem()); - assertEquals("display", coding.getDisplay()); - break; - case "value": - assertEquals("some value", designationComponent.getValue().toString()); - break; - } - } - } - } - } - - @Test - public void testValidateCode_SystemCodeDisplayUrl_Error() { + public void testValidateCode_forSystemCodeWithError_returnsCorrectly() { createNextValueSetReturnParameters(false, null, ERROR_MESSAGE); IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, VALUE_SET_URL); - assertEquals(null, outcome.getCode()); - assertEquals(null, outcome.getDisplay()); + assertNotNull(outcome); + assertNull(outcome.getCode()); + assertNull(outcome.getDisplay()); assertEquals(IValidationSupport.IssueSeverity.ERROR, outcome.getSeverity()); assertEquals(ERROR_MESSAGE, outcome.getMessage()); @@ -258,18 +236,24 @@ public class RemoteTerminologyServiceValidationSupportTest { assertEquals(DISPLAY, myValueSetProvider.myLastDisplay.getValue()); assertEquals(CODE_SYSTEM, myValueSetProvider.myLastSystem.getValue()); assertEquals(VALUE_SET_URL, myValueSetProvider.myLastUrl.getValue()); - assertEquals(null, myValueSetProvider.myLastValueSet); + assertNull(myValueSetProvider.myLastValueSet); } @Test - public void testValidateCodeInCodeSystem_Good() { - createNextCodeSystemReturnParameters(true, DISPLAY, null); + public void testValidateCode_forCodeSystem_returnsCorrectly() { + myCodeSystemProvider.myNextValidationResult = new IValidationSupport.CodeValidationResult(); + myCodeSystemProvider.myNextValidationResult.setCodeSystemVersion(CODE_SYSTEM); + myCodeSystemProvider.myNextValidationResult.setCode(CODE); + myCodeSystemProvider.myNextValidationResult.setCodeSystemName(CODE_SYSTEM_NAME); + myCodeSystemProvider.myNextValidationResult.setDisplay(DISPLAY); + myCodeSystemProvider.myNextValidationResult.setMessage(SUCCESS_MESSAGE); IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, null); + assertNotNull(outcome); assertEquals(CODE, outcome.getCode()); assertEquals(DISPLAY, outcome.getDisplay()); - assertEquals(null, outcome.getSeverity()); - assertEquals(null, outcome.getMessage()); + assertNull(outcome.getSeverity()); + assertNull(outcome.getMessage()); assertEquals(CODE, myCodeSystemProvider.myLastCode.getCode()); assertEquals(CODE_SYSTEM, myCodeSystemProvider.myLastUrl.getValueAsString()); @@ -284,16 +268,17 @@ public class RemoteTerminologyServiceValidationSupportTest { valueSet.setUrl(VALUE_SET_URL); IValidationSupport.CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, new ConceptValidationOptions(), CODE_SYSTEM, CODE, DISPLAY, valueSet); + assertNotNull(outcome); assertEquals(CODE, outcome.getCode()); assertEquals(DISPLAY, outcome.getDisplay()); - assertEquals(null, outcome.getSeverity()); - assertEquals(null, outcome.getMessage()); + assertNull(outcome.getSeverity()); + assertNull(outcome.getMessage()); assertEquals(CODE, myValueSetProvider.myLastCode.getCode()); assertEquals(DISPLAY, myValueSetProvider.myLastDisplay.getValue()); assertEquals(CODE_SYSTEM, myValueSetProvider.myLastSystem.getValue()); assertEquals(VALUE_SET_URL, myValueSetProvider.myLastUrl.getValueAsString()); - assertEquals(null, myValueSetProvider.myLastValueSet); + assertNull(myValueSetProvider.myLastValueSet); } /** @@ -307,7 +292,7 @@ public class RemoteTerminologyServiceValidationSupportTest { valueSet.setUrl(VALUE_SET_URL); IValidationSupport.CodeValidationResult outcome = mySvc.validateCodeInValueSet(null, new ConceptValidationOptions().setInferSystem(true), null, CODE, DISPLAY, valueSet); - assertEquals(null, outcome); + assertNull(outcome); } @Test @@ -350,7 +335,8 @@ public class RemoteTerminologyServiceValidationSupportTest { TranslateConceptResults results = mySvc.translateConcept(request); - assertEquals(results.getResult(), true); + assertNotNull(results); + assertTrue(results.getResult()); assertEquals(results.getResults().size(), 2); for(TranslateConceptResult result : results.getResults()) { assertEquals(singleResult, result); @@ -374,8 +360,8 @@ public class RemoteTerminologyServiceValidationSupportTest { IValidationSupport.TranslateCodeRequest request = new IValidationSupport.TranslateCodeRequest(codings, null); TranslateConceptResults results = mySvc.translateConcept(request); - - assertEquals(results.getResult(), false); + assertNotNull(results); + assertFalse(results.getResult()); assertEquals(results.getResults().size(), 0); assertNull(myConceptMapProvider.myLastCodeableConcept); @@ -393,7 +379,7 @@ public class RemoteTerminologyServiceValidationSupportTest { myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>(); // when - IBaseResource codeSystem = mySvc.fetchCodeSystem("http://loinc.org"); + mySvc.fetchCodeSystem("http://loinc.org"); // then assertEquals(SummaryEnum.FALSE, myCodeSystemProvider.myLastSummaryParam); @@ -535,7 +521,7 @@ public class RemoteTerminologyServiceValidationSupportTest { /** * Captures the system parameter of the request */ - private class TestClientInterceptor implements IClientInterceptor { + private static class TestClientInterceptor implements IClientInterceptor { private String capturedSystemParameter; @@ -552,12 +538,12 @@ public class RemoteTerminologyServiceValidationSupportTest { capturedSystemParameter = systemValues.get(0); } } catch (IOException theE) { - theE.printStackTrace(); + // ignore } } @Override - public void interceptResponse(IHttpResponse theResponse) throws IOException { } + public void interceptResponse(IHttpResponse theResponse) { } public String getCapturedSystemParameter() { return capturedSystemParameter; } } @@ -570,7 +556,7 @@ public class RemoteTerminologyServiceValidationSupportTest { myValueSetProvider.myNextReturnValueSets = new ArrayList<>(); boolean outcome = mySvc.isValueSetSupported(null, "http://loinc.org/VS"); - assertEquals(false, outcome); + assertFalse(outcome); assertEquals("http://loinc.org/VS", myValueSetProvider.myLastUrlParam.getValue()); } @@ -580,7 +566,7 @@ public class RemoteTerminologyServiceValidationSupportTest { myValueSetProvider.myNextReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/123")); boolean outcome = mySvc.isValueSetSupported(null, "http://loinc.org/VS"); - assertEquals(true, outcome); + assertTrue(outcome); assertEquals("http://loinc.org/VS", myValueSetProvider.myLastUrlParam.getValue()); } @@ -589,7 +575,7 @@ public class RemoteTerminologyServiceValidationSupportTest { myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>(); boolean outcome = mySvc.isCodeSystemSupported(null, "http://loinc.org"); - assertEquals(false, outcome); + assertFalse(outcome); assertEquals("http://loinc.org", myCodeSystemProvider.myLastUrlParam.getValue()); } @@ -599,31 +585,10 @@ public class RemoteTerminologyServiceValidationSupportTest { myCodeSystemProvider.myNextReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/123")); boolean outcome = mySvc.isCodeSystemSupported(null, "http://loinc.org"); - assertEquals(true, outcome); + assertTrue(outcome); assertEquals("http://loinc.org", myCodeSystemProvider.myLastUrlParam.getValue()); } - private void createNextCodeSystemReturnParameters(boolean theResult, String theDisplay, String theMessage) { - myCodeSystemProvider.myNextReturnParams = new Parameters(); - myCodeSystemProvider.myNextReturnParams.addParameter("result", theResult); - myCodeSystemProvider.myNextReturnParams.addParameter("display", theDisplay); - if (theMessage != null) { - myCodeSystemProvider.myNextReturnParams.addParameter("message", theMessage); - } - } - - private void createNextCodeSystemLookupReturnParameters(boolean theResult, String theVersion, String theVersionAsText, - String theDisplay, String theMessage) { - myCodeSystemProvider.myNextReturnParams = new Parameters(); - myCodeSystemProvider.myNextReturnParams.addParameter("result", theResult); - myCodeSystemProvider.myNextReturnParams.addParameter("version", theVersion); - myCodeSystemProvider.myNextReturnParams.addParameter("name", theVersionAsText); - myCodeSystemProvider.myNextReturnParams.addParameter("display", theDisplay); - if (theMessage != null) { - myCodeSystemProvider.myNextReturnParams.addParameter("message", theMessage); - } - } - private void createNextValueSetReturnParameters(boolean theResult, String theDisplay, String theMessage) { myValueSetProvider.myNextReturnParams = new Parameters(); myValueSetProvider.myNextReturnParams.addParameter("result", theResult); @@ -633,53 +598,33 @@ public class RemoteTerminologyServiceValidationSupportTest { } } - private void addAdditionalReturnParameters() { - // property - Parameters.ParametersParameterComponent param = myCodeSystemProvider.myNextReturnParams.addParameter().setName("property"); - param.addPart().setName("name").setValue(new StringType("birthDate")); - param.addPart().setName("value").setValue(new StringType("1930-01-01")); - // designation - param = myCodeSystemProvider.myNextReturnParams.addParameter().setName("designation"); - param.addPart().setName("language").setValue(new CodeType("en")); - Parameters.ParametersParameterComponent codingParam = param.addPart().setName("use"); - Coding coding = new Coding(); - coding.setCode("code"); - coding.setSystem("system"); - coding.setDisplay("display"); - codingParam.setValue(coding); - param.addPart().setName("value").setValue(new StringType("some value")); - } - private static class MyCodeSystemProvider implements IResourceProvider { private SummaryEnum myLastSummaryParam; private UriParam myLastUrlParam; private List myNextReturnCodeSystems; - private int myInvocationCount; private UriType myLastUrl; private CodeType myLastCode; - private Coding myLastCoding; - private StringType myLastVersion; private Parameters myNextReturnParams; private IValidationSupport.LookupCodeResult myNextLookupCodeResult; + private IValidationSupport.CodeValidationResult myNextValidationResult; @Operation(name = "validate-code", idempotent = true, returnParameters = { @OperationParam(name = "result", type = BooleanType.class, min = 1), @OperationParam(name = "message", type = StringType.class), @OperationParam(name = "display", type = StringType.class) }) - public Parameters validateCode( + public IBaseParameters validateCode( HttpServletRequest theServletRequest, @IdParam(optional = true) IdType theId, @OperationParam(name = "url", min = 0, max = 1) UriType theCodeSystemUrl, @OperationParam(name = "code", min = 0, max = 1) CodeType theCode, @OperationParam(name = "display", min = 0, max = 1) StringType theDisplay ) { - myInvocationCount++; myLastUrl = theCodeSystemUrl; myLastCode = theCode; + myNextReturnParams = (Parameters)myNextValidationResult.toParameters(ourCtx); return myNextReturnParams; - } @Operation(name = JpaConstants.OPERATION_LOOKUP, idempotent = true, returnParameters= { @@ -687,22 +632,21 @@ public class RemoteTerminologyServiceValidationSupportTest { @OperationParam(name="version", type=StringType.class, min=0), @OperationParam(name="display", type=StringType.class, min=1), @OperationParam(name="abstract", type=BooleanType.class, min=1), + @OperationParam(name="property", min = 0, max = OperationParam.MAX_UNLIMITED) }) - public Parameters lookup( + public IBaseParameters lookup( HttpServletRequest theServletRequest, @OperationParam(name="code", min=0, max=1) CodeType theCode, @OperationParam(name="system", min=0, max=1) UriType theSystem, @OperationParam(name="coding", min=0, max=1) Coding theCoding, @OperationParam(name="version", min=0, max=1) StringType theVersion, @OperationParam(name="displayLanguage", min=0, max=1) CodeType theDisplayLanguage, - @OperationParam(name="property", min = 0, max = OperationParam.MAX_UNLIMITED) List theProperties, + @OperationParam(name="property", min = 0, max = OperationParam.MAX_UNLIMITED) List thePropertyNames, RequestDetails theRequestDetails ) { - myInvocationCount++; myLastCode = theCode; myLastUrl = theSystem; - myLastCoding = theCoding; - myLastVersion = theVersion; + myNextReturnParams = (Parameters)myNextLookupCodeResult.toParameters(theRequestDetails.getFhirContext(), thePropertyNames); return myNextReturnParams; } @@ -779,8 +723,6 @@ public class RemoteTerminologyServiceValidationSupportTest { private UriType myLastTargetValueSet; private UriType myLastTargetCodeSystem; private BooleanType myLastReverse; - - private int myInvocationCount; private Parameters myNextReturnParams; @Operation(name = JpaConstants.OPERATION_TRANSLATE, idempotent = true, returnParameters = { @@ -799,7 +741,6 @@ public class RemoteTerminologyServiceValidationSupportTest { @OperationParam(name = "reverse", min = 0, max = 1) BooleanType theReverse, RequestDetails theRequestDetails ) { - myInvocationCount++; myLastConceptMapUrl = theConceptMapUrl; myLastConceptMapVersion = theConceptMapVersion; myLastCodeableConcept = theSourceCodeableConcept; diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyServiceValidationSupportDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyServiceValidationSupportDstu3Test.java index 9d435406dd8..017eea69f19 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyServiceValidationSupportDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/RemoteTerminologyServiceValidationSupportDstu3Test.java @@ -2,6 +2,7 @@ package org.hl7.fhir.dstu3.hapi.validation; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; @@ -9,13 +10,11 @@ import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; -import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.hl7.fhir.dstu3.model.BooleanType; import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeType; import org.hl7.fhir.dstu3.model.Coding; -import org.hl7.fhir.dstu3.model.DateType; import org.hl7.fhir.dstu3.model.Parameters; import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.dstu3.model.UriType; @@ -25,7 +24,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import javax.servlet.http.HttpServletRequest; +import java.util.Iterator; import java.util.List; +import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -34,14 +35,13 @@ public class RemoteTerminologyServiceValidationSupportDstu3Test { private static final String DISPLAY = "DISPLAY"; private static final String LANGUAGE = "en"; private static final String CODE_SYSTEM = "CODE_SYS"; - private static final String CODE_SYSTEM_VERSION = "2.1"; - private static final String CODE_SYSTEM_VERSION_AS_TEXT = "v2.1.12"; + private static final String CODE_SYSTEM_NAME = "Code System"; private static final String CODE = "CODE"; - private static FhirContext ourCtx = FhirContext.forDstu3(); + private static final FhirContext ourCtx = FhirContext.forDstu3(); @RegisterExtension - public RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(ourCtx); + public static RestfulServerExtension ourRestfulServerExtension = new RestfulServerExtension(ourCtx); private RemoteTerminologyServiceValidationSupport mySvc; private MyCodeSystemProvider myCodeSystemProvider; @@ -49,143 +49,98 @@ public class RemoteTerminologyServiceValidationSupportDstu3Test { @BeforeEach public void before() { myCodeSystemProvider = new MyCodeSystemProvider(); - myRestfulServerExtension.getRestfulServer().registerProvider(myCodeSystemProvider); - String baseUrl = "http://localhost:" + myRestfulServerExtension.getPort(); + ourRestfulServerExtension.getRestfulServer().registerProvider(myCodeSystemProvider); + String baseUrl = "http://localhost:" + ourRestfulServerExtension.getPort(); mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx); mySvc.setBaseUrl(baseUrl); mySvc.addClientInterceptor(new LoggingInterceptor(true)); } - @Test - public void testLookupOperation_CodeSystem_Success() { - createNextCodeSystemLookupReturnParameters(true, CODE_SYSTEM_VERSION, CODE_SYSTEM_VERSION_AS_TEXT, DISPLAY); - - IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, CODE_SYSTEM, CODE); - assertNotNull(outcome, "Call to lookupCode() should return a non-NULL result!"); - assertEquals(DISPLAY, outcome.getCodeDisplay()); - assertEquals(CODE_SYSTEM_VERSION, outcome.getCodeSystemVersion()); - - assertEquals(CODE, myCodeSystemProvider.myLastCode.asStringValue()); - assertEquals(CODE_SYSTEM, myCodeSystemProvider.myLastUrl.getValueAsString()); - for (Parameters.ParametersParameterComponent param : myCodeSystemProvider.myNextReturnParams.getParameter()) { - String paramName = param.getName(); - if (paramName.equals("result")) { - assertEquals(true, ((BooleanType)param.getValue()).booleanValue()); - } else if (paramName.equals("version")) { - assertEquals(CODE_SYSTEM_VERSION, param.getValue().toString()); - } else if (paramName.equals("display")) { - assertEquals(DISPLAY, param.getValue().toString()); - } else if (paramName.equals("name")) { - assertEquals(CODE_SYSTEM_VERSION_AS_TEXT, param.getValue().toString()); - } - } - } - @Test public void testLookupOperationWithAllParams_CodeSystem_Success() { - createNextCodeSystemLookupReturnParameters(true, CODE_SYSTEM_VERSION, CODE_SYSTEM_VERSION_AS_TEXT, DISPLAY, LANGUAGE); - addAdditionalCodeSystemLookupReturnParameters(); + myCodeSystemProvider.myNextLookupCodeResult = new IValidationSupport.LookupCodeResult(); + myCodeSystemProvider.myNextLookupCodeResult.setFound(true); + myCodeSystemProvider.myNextLookupCodeResult.setCodeSystemVersion(CODE_SYSTEM); + myCodeSystemProvider.myNextLookupCodeResult.setSearchedForCode(CODE); + myCodeSystemProvider.myNextLookupCodeResult.setCodeSystemDisplayName(CODE_SYSTEM_NAME); + myCodeSystemProvider.myNextLookupCodeResult.setCodeDisplay(DISPLAY); - IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, CODE_SYSTEM, CODE, LANGUAGE); + // property + String propertyName = "birthDate"; + String propertyValue = "1930-01-01"; + IValidationSupport.BaseConceptProperty property = new IValidationSupport.StringConceptProperty(propertyName, propertyValue); + myCodeSystemProvider.myNextLookupCodeResult.getProperties().add(property); + + // designation + IValidationSupport.ConceptDesignation designation = new IValidationSupport.ConceptDesignation(); + designation.setLanguage("en"); + designation.setUseCode("code"); + designation.setUseSystem("system"); + designation.setUseDisplay("display"); + designation.setValue("some value"); + myCodeSystemProvider.myNextLookupCodeResult.getDesignations().add(designation); + + IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, new LookupCodeRequest(CODE_SYSTEM, CODE, LANGUAGE, Set.of("birthDate"))); assertNotNull(outcome, "Call to lookupCode() should return a non-NULL result!"); assertEquals(DISPLAY, outcome.getCodeDisplay()); - assertEquals(CODE_SYSTEM_VERSION, outcome.getCodeSystemVersion()); + assertEquals(CODE_SYSTEM, outcome.getCodeSystemVersion()); assertEquals(CODE, myCodeSystemProvider.myLastCode.asStringValue()); assertEquals(CODE_SYSTEM, myCodeSystemProvider.myLastUrl.getValueAsString()); - for (Parameters.ParametersParameterComponent param : myCodeSystemProvider.myNextReturnParams.getParameter()) { - String paramName = param.getName(); - if (paramName.equals("result")) { - assertEquals(true, ((BooleanType)param.getValue()).booleanValue()); - } else if (paramName.equals("version")) { - assertEquals(CODE_SYSTEM_VERSION, param.getValue().toString()); - } else if (paramName.equals("display")) { - assertEquals(DISPLAY, param.getValue().toString()); - } else if (paramName.equals("name")) { - assertEquals(CODE_SYSTEM_VERSION_AS_TEXT, param.getValue().toString()); - } else if (paramName.equals("language")) { - assertEquals(LANGUAGE, param.getValue().toString()); - } else if (paramName.equals("property")) { - for (org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent propertyComponent : param.getPart()) { - switch(propertyComponent.getName()) { - case "name": - assertEquals("birthDate", propertyComponent.getValue().toString()); - break; - case "value": - assertEquals("1930-01-01", ((DateType)propertyComponent.getValue()).asStringValue()); - break; - } - } - } else if (paramName.equals("designation")) { - for (org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent designationComponent : param.getPart()) { - switch(designationComponent.getName()) { - case "language": - assertEquals(LANGUAGE, designationComponent.getValue().toString()); - break; - case "use": - Coding coding = (Coding)designationComponent.getValue(); - assertNotNull(coding, "Coding value returned via designation use should NOT be NULL!"); - assertEquals("code", coding.getCode()); - assertEquals("system", coding.getSystem()); - assertEquals("display", coding.getDisplay()); - break; - case "value": - assertEquals("some value", designationComponent.getValue().toString()); - break; - } - } + + Parameters.ParametersParameterComponent propertyComponent = null; + Parameters.ParametersParameterComponent designationComponent = null; + for (Parameters.ParametersParameterComponent parameterComponent : myCodeSystemProvider.myNextReturnParams.getParameter()) { + if ("property".equals(parameterComponent.getName())) { + propertyComponent = parameterComponent; + } + if ("designation".equals(parameterComponent.getName())) { + designationComponent = parameterComponent; } } - } - private void createNextCodeSystemLookupReturnParameters(boolean theResult, String theVersion, String theVersionAsText, - String theDisplay) { - createNextCodeSystemLookupReturnParameters(theResult, theVersion, theVersionAsText, theDisplay, null); - } + assertNotNull(propertyComponent); - private void createNextCodeSystemLookupReturnParameters(boolean theResult, String theVersion, String theVersionAsText, - String theDisplay, String theLanguage) { - myCodeSystemProvider.myNextReturnParams = new Parameters(); - myCodeSystemProvider.myNextReturnParams.addParameter().setName("result").setValue(new BooleanType(theResult)); - myCodeSystemProvider.myNextReturnParams.addParameter().setName("version").setValue(new StringType(theVersion)); - myCodeSystemProvider.myNextReturnParams.addParameter().setName("name").setValue(new StringType(theVersionAsText)); - myCodeSystemProvider.myNextReturnParams.addParameter().setName("display").setValue(new StringType(theDisplay)); - if (!StringUtils.isBlank(theLanguage)) { - myCodeSystemProvider.myNextReturnParams.addParameter().setName("language").setValue(new StringType(theLanguage)); - } - } + Iterator propertyComponentIterator = propertyComponent.getPart().iterator(); + propertyComponent = propertyComponentIterator.next(); + assertEquals("code", propertyComponent.getName()); + assertEquals(propertyName, ((StringType)propertyComponent.getValue()).getValue()); - private void addAdditionalCodeSystemLookupReturnParameters() { - // property - Parameters.ParametersParameterComponent param = myCodeSystemProvider.myNextReturnParams.addParameter().setName("property"); - param.addPart().setName("name").setValue(new StringType("birthDate")); - param.addPart().setName("value").setValue(new DateType("1930-01-01")); - // designation - param = myCodeSystemProvider.myNextReturnParams.addParameter().setName("designation"); - param.addPart().setName("language").setValue(new CodeType("en")); - Parameters.ParametersParameterComponent codingParam = param.addPart().setName("use"); - Coding coding = new Coding(); - coding.setCode("code"); - coding.setSystem("system"); - coding.setDisplay("display"); - codingParam.setValue(coding); - param.addPart().setName("value").setValue(new StringType("some value")); + propertyComponent = propertyComponentIterator.next(); + assertEquals("value", propertyComponent.getName()); + assertEquals(propertyValue, ((StringType)propertyComponent.getValue()).getValue()); + + assertNotNull(designationComponent); + Iterator partParameter = designationComponent.getPart().iterator(); + designationComponent = partParameter.next(); + assertEquals("language", designationComponent.getName()); + assertEquals(LANGUAGE, designationComponent.getValue().toString()); + + designationComponent = partParameter.next(); + assertEquals("use", designationComponent.getName()); + Coding coding = (Coding)designationComponent.getValue(); + assertNotNull(coding, "Coding value returned via designation use should NOT be NULL!"); + assertEquals("code", coding.getCode()); + assertEquals("system", coding.getSystem()); + assertEquals("display", coding.getDisplay()); + + designationComponent = partParameter.next(); + assertEquals("value", designationComponent.getName()); + assertEquals("some value", designationComponent.getValue().toString()); } private static class MyCodeSystemProvider implements IResourceProvider { - private int myInvocationCount; private UriType myLastUrl; private CodeType myLastCode; - private Coding myLastCoding; - private StringType myLastVersion; - private CodeType myLastDisplayLanguage; private Parameters myNextReturnParams; + private IValidationSupport.LookupCodeResult myNextLookupCodeResult; @Operation(name = JpaConstants.OPERATION_LOOKUP, idempotent = true, returnParameters= { @OperationParam(name="name", type=StringType.class, min=1), @OperationParam(name="version", type=StringType.class, min=0), @OperationParam(name="display", type=StringType.class, min=1), @OperationParam(name="abstract", type=BooleanType.class, min=1), + @OperationParam(name="property", min = 0, max = OperationParam.MAX_UNLIMITED) }) public Parameters lookup( HttpServletRequest theServletRequest, @@ -194,15 +149,13 @@ public class RemoteTerminologyServiceValidationSupportDstu3Test { @OperationParam(name="coding", min=0, max=1) Coding theCoding, @OperationParam(name="version", min=0, max=1) StringType theVersion, @OperationParam(name="displayLanguage", min=0, max=1) CodeType theDisplayLanguage, - @OperationParam(name="property", min = 0, max = OperationParam.MAX_UNLIMITED) List theProperties, + @OperationParam(name="property", min = 0, max = OperationParam.MAX_UNLIMITED) List thePropertyNames, RequestDetails theRequestDetails ) { - myInvocationCount++; myLastCode = theCode; myLastUrl = theSystem; - myLastCoding = theCoding; - myLastVersion = theVersion; - myLastDisplayLanguage = theDisplayLanguage; + + myNextReturnParams = (Parameters)myNextLookupCodeResult.toParameters(theRequestDetails.getFhirContext(), thePropertyNames); return myNextReturnParams; } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java index 4484737c1c8..7f29442a01a 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.rest.api.Constants; @@ -264,9 +265,10 @@ public class FhirInstanceValidatorR4Test extends BaseTest { return retVal; } }); - when(myMockSupport.lookupCode(any(), any(), any(), any())).thenAnswer(t -> { - String system = t.getArgument(1, String.class); - String code = t.getArgument(2, String.class); + when(myMockSupport.lookupCode(any(), any())).thenAnswer(t -> { + LookupCodeRequest request = t.getArgument(1, LookupCodeRequest.class); + String system = request.getSystem(); + String code = request.getCode(); if (myValidConcepts.contains(system + "___" + code)) { return new IValidationSupport.LookupCodeResult().setFound(true); } else { diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4b/validation/FhirInstanceValidatorR4BTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4b/validation/FhirInstanceValidatorR4BTest.java index 97f735b9ebb..f700c6e6744 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4b/validation/FhirInstanceValidatorR4BTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4b/validation/FhirInstanceValidatorR4BTest.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.rest.api.Constants; @@ -31,9 +32,33 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4b.conformance.ProfileUtilities; import org.hl7.fhir.r4b.context.IWorkerContext; import org.hl7.fhir.r4b.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r4b.model.*; +import org.hl7.fhir.r4b.model.AllergyIntolerance; +import org.hl7.fhir.r4b.model.Base; +import org.hl7.fhir.r4b.model.Base64BinaryType; +import org.hl7.fhir.r4b.model.BooleanType; +import org.hl7.fhir.r4b.model.Bundle; import org.hl7.fhir.r4b.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.r4b.model.CodeSystem; +import org.hl7.fhir.r4b.model.CodeType; +import org.hl7.fhir.r4b.model.Consent; +import org.hl7.fhir.r4b.model.ContactPoint; +import org.hl7.fhir.r4b.model.DateTimeType; +import org.hl7.fhir.r4b.model.Enumerations; +import org.hl7.fhir.r4b.model.Extension; +import org.hl7.fhir.r4b.model.Media; +import org.hl7.fhir.r4b.model.Narrative; +import org.hl7.fhir.r4b.model.Observation; +import org.hl7.fhir.r4b.model.OperationOutcome; +import org.hl7.fhir.r4b.model.Patient; +import org.hl7.fhir.r4b.model.Period; +import org.hl7.fhir.r4b.model.Practitioner; +import org.hl7.fhir.r4b.model.Procedure; +import org.hl7.fhir.r4b.model.QuestionnaireResponse; +import org.hl7.fhir.r4b.model.Reference; +import org.hl7.fhir.r4b.model.StringType; +import org.hl7.fhir.r4b.model.StructureDefinition; import org.hl7.fhir.r4b.model.StructureDefinition.StructureDefinitionKind; +import org.hl7.fhir.r4b.model.ValueSet; import org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.r4b.terminologies.ValueSetExpander; import org.hl7.fhir.r4b.utils.FHIRPathEngine; @@ -72,7 +97,6 @@ import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.startsWith; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -230,9 +254,10 @@ public class FhirInstanceValidatorR4BTest extends BaseTest { return retVal; } }); - when(myMockSupport.lookupCode(any(), any(), any(), any())).thenAnswer(t -> { - String system = t.getArgument(1, String.class); - String code = t.getArgument(2, String.class); + when(myMockSupport.lookupCode(any(), any())).thenAnswer(t -> { + LookupCodeRequest request = t.getArgument(1, LookupCodeRequest.class); + String system = request.getSystem(); + String code = request.getCode(); if (myValidConcepts.contains(system + "___" + code)) { return new IValidationSupport.LookupCodeResult().setFound(true); } else { diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/RemoteTerminologyServiceValidationSupportR5Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/RemoteTerminologyServiceValidationSupportR5Test.java index 2d5c71bce56..0e9485bae4d 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/RemoteTerminologyServiceValidationSupportR5Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/RemoteTerminologyServiceValidationSupportR5Test.java @@ -1,8 +1,8 @@ package org.hl7.fhir.r5.validation; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.ValidationSupportContext; +import ca.uhn.fhir.context.support.LookupCodeRequest; import ca.uhn.fhir.test.utilities.server.RestfulServerExtension; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.junit.jupiter.api.Assertions; @@ -12,9 +12,9 @@ import org.junit.jupiter.api.extension.RegisterExtension; public class RemoteTerminologyServiceValidationSupportR5Test { private static final String ANY_NONBLANK_VALUE = "anything"; - private static FhirContext ourCtx = FhirContext.forR5Cached(); + private static final FhirContext ourCtx = FhirContext.forR5Cached(); @RegisterExtension - public RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(ourCtx); + public static RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(ourCtx); private RemoteTerminologyServiceValidationSupport mySvc; @@ -27,9 +27,9 @@ public class RemoteTerminologyServiceValidationSupportR5Test { @Test public void testLookupCode_R5_ThrowsException() { - Assertions.assertThrows(UnsupportedOperationException.class, () -> { - IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode( - new ValidationSupportContext(ourCtx.getValidationSupport()), ANY_NONBLANK_VALUE, ANY_NONBLANK_VALUE); - }); + Assertions.assertThrows(UnsupportedOperationException.class, + () -> mySvc.lookupCode( + new ValidationSupportContext(ourCtx.getValidationSupport()), + new LookupCodeRequest(ANY_NONBLANK_VALUE, ANY_NONBLANK_VALUE))); } } From 6e683405a1840cbfc4d0006f4ab81719e3f83c88 Mon Sep 17 00:00:00 2001 From: TipzCM Date: Tue, 28 Nov 2023 16:28:17 -0500 Subject: [PATCH 03/14] 5237 fixing empty last page if even results (#5506) paging will not return empty pages if no results left --- .../ca/uhn/fhir/cli/BulkImportCommandIT.java | 11 +- .../5192-include-in-search-paging-fix.yaml | 7 ++ .../search/PersistedJpaBundleProvider.java | 20 +++- ...istedJpaSearchFirstPageBundleProvider.java | 16 ++- .../jpa/search/SynchronousSearchSvcImpl.java | 25 +++- .../search/SynchronousSearchSvcImplTest.java | 16 ++- .../jpa/dao/r4/ConsentEventsDaoR4Test.java | 38 ++++-- .../r4/FhirResourceDaoR4QueryCountTest.java | 23 +--- .../ForceOffsetSearchModeInterceptorTest.java | 43 +------ ...sourceProviderCustomSearchParamR4Test.java | 7 +- .../r4/ResourceProviderR4EverythingTest.java | 1 - .../provider/r4/ResourceProviderR4Test.java | 100 +++++++++++++++- .../ca/uhn/fhir/jpa/test/BaseJpaR4Test.java | 5 +- .../jpa/test/config/TestHapiJpaConfig.java | 21 ++++ .../fhir/jpa/test/config/TestR4Config.java | 3 +- .../fhir/rest/api/server/IBundleProvider.java | 1 + .../rest/server/SimpleBundleProvider.java | 14 +++ .../server/method/ResponseBundleBuilder.java | 8 +- .../fhir/rest/server/method/ResponsePage.java | 111 +++++++++++++++--- .../api/server/method/ResponsePageTest.java | 59 +++++++--- .../jobs/imprt/BulkDataImportProvider.java | 6 +- 21 files changed, 403 insertions(+), 132 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5192-include-in-search-paging-fix.yaml create mode 100644 hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/config/TestHapiJpaConfig.java diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/BulkImportCommandIT.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/BulkImportCommandIT.java index 02d3e24325b..c7f4369faef 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/BulkImportCommandIT.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/BulkImportCommandIT.java @@ -8,6 +8,8 @@ import ca.uhn.fhir.batch2.model.JobInstanceStartRequest; import ca.uhn.fhir.batch2.model.StatusEnum; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.batch.models.Batch2JobStartResponse; +import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc; +import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; import ca.uhn.fhir.system.HapiSystemProperties; @@ -66,6 +68,8 @@ public class BulkImportCommandIT { private IJobCoordinator myJobCoordinator; private final BulkDataImportProvider myProvider = new BulkDataImportProvider(); private final FhirContext myCtx = FhirContext.forR4Cached(); + @Mock + private IRequestPartitionHelperSvc myRequestPartitionHelperSvc; @RegisterExtension public RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(myCtx, myProvider) .registerInterceptor(new LoggingInterceptor()); @@ -77,6 +81,7 @@ public class BulkImportCommandIT { public void beforeEach() throws IOException { myProvider.setFhirContext(myCtx); myProvider.setJobCoordinator(myJobCoordinator); + myProvider.setRequestPartitionHelperService(myRequestPartitionHelperSvc); myTempDir = Files.createTempDirectory("hapifhir"); ourLog.info("Created temp directory: {}", myTempDir); } @@ -123,7 +128,7 @@ public class BulkImportCommandIT { await().until(() -> myRestfulServerExtension.getRequestContentTypes().size(), equalTo(2)); ourLog.info("Initiation requests complete"); - verify(myJobCoordinator, timeout(10000).times(1)).startInstance(myStartCaptor.capture()); + verify(myJobCoordinator, timeout(10000).times(1)).startInstance(any(RequestDetails.class), myStartCaptor.capture()); JobInstanceStartRequest startRequest = myStartCaptor.getValue(); BulkImportJobParameters jobParameters = startRequest.getParameters(BulkImportJobParameters.class); @@ -165,7 +170,7 @@ public class BulkImportCommandIT { await().until(() -> myRestfulServerExtension.getRequestContentTypes().size(), equalTo(2)); ourLog.info("Initiation requests complete"); - verify(myJobCoordinator, timeout(10000).times(1)).startInstance(myStartCaptor.capture()); + verify(myJobCoordinator, timeout(10000).times(1)).startInstance(any(RequestDetails.class), myStartCaptor.capture()); JobInstanceStartRequest startRequest = myStartCaptor.getValue(); BulkImportJobParameters jobParameters = startRequest.getParameters(BulkImportJobParameters.class); @@ -206,7 +211,7 @@ public class BulkImportCommandIT { await().until(() -> myRestfulServerExtension.getRequestContentTypes().size(), equalTo(2)); ourLog.info("Initiation requests complete"); - verify(myJobCoordinator, timeout(10000).times(1)).startInstance(myStartCaptor.capture()); + verify(myJobCoordinator, timeout(10000).times(1)).startInstance(any(RequestDetails.class), myStartCaptor.capture()); try{ JobInstanceStartRequest startRequest = myStartCaptor.getValue(); diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5192-include-in-search-paging-fix.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5192-include-in-search-paging-fix.yaml new file mode 100644 index 00000000000..a63588d0a9f --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5192-include-in-search-paging-fix.yaml @@ -0,0 +1,7 @@ +--- +type: fix +issue: 5192 +title: "Fixed a bug where search Bundles with `include` entries from an _include query parameter might + trigger a 'next' link to blank pages when + no more results `match` results are available. +" diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java index 5d2dae49723..51a994e7afa 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java @@ -125,7 +125,7 @@ public class PersistedJpaBundleProvider implements IBundleProvider { * of this class, since it's a prototype */ private Search mySearchEntity; - private String myUuid; + private final String myUuid; private SearchCacheStatusEnum myCacheStatus; private RequestPartitionId myRequestPartitionId; @@ -259,13 +259,21 @@ public class PersistedJpaBundleProvider implements IBundleProvider { final ISearchBuilder sb = mySearchBuilderFactory.newSearchBuilder(dao, resourceName, resourceType); RequestPartitionId requestPartitionId = getRequestPartitionId(); - final List pidsSubList = - mySearchCoordinatorSvc.getResources(myUuid, theFromIndex, theToIndex, myRequest, requestPartitionId); + // we request 1 more resource than we need + // this is so we can be sure of when we hit the last page + // (when doing offset searches) + final List pidsSubList = mySearchCoordinatorSvc.getResources( + myUuid, theFromIndex, theToIndex + 1, myRequest, requestPartitionId); + // max list size should be either the entire list, or from - to length + int maxSize = Math.min(theToIndex - theFromIndex, pidsSubList.size()); + theResponsePageBuilder.setTotalRequestedResourcesFetched(pidsSubList.size()); + + List firstBatchOfPids = pidsSubList.subList(0, maxSize); List resources = myTxService .withRequest(myRequest) .withRequestPartitionId(requestPartitionId) .execute(() -> { - return toResourceList(sb, pidsSubList, theResponsePageBuilder); + return toResourceList(sb, firstBatchOfPids, theResponsePageBuilder); }); return resources; @@ -541,8 +549,8 @@ public class PersistedJpaBundleProvider implements IBundleProvider { // this can (potentially) change the results being returned. int precount = resources.size(); resources = ServerInterceptorUtil.fireStoragePreshowResource(resources, myRequest, myInterceptorBroadcaster); - // we only care about omitted results from *this* page - theResponsePageBuilder.setToOmittedResourceCount(precount - resources.size()); + // we only care about omitted results from this page + theResponsePageBuilder.setOmittedResourceCount(precount - resources.size()); theResponsePageBuilder.setResources(resources); theResponsePageBuilder.setIncludedResourceCount(includedPidList.size()); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java index b1ac13fcbe2..f2357932a0e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java @@ -73,16 +73,23 @@ public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundl mySearchTask.awaitInitialSync(); + // request 1 more than we need to, in order to know if there are extra values ourLog.trace("Fetching search resource PIDs from task: {}", mySearchTask.getClass()); - final List pids = mySearchTask.getResourcePids(theFromIndex, theToIndex); + final List pids = mySearchTask.getResourcePids(theFromIndex, theToIndex + 1); ourLog.trace("Done fetching search resource PIDs"); + int countOfPids = pids.size(); + ; + int maxSize = Math.min(theToIndex - theFromIndex, countOfPids); + thePageBuilder.setTotalRequestedResourcesFetched(countOfPids); + RequestPartitionId requestPartitionId = getRequestPartitionId(); + List firstBatch = pids.subList(0, maxSize); List retVal = myTxService .withRequest(myRequest) .withRequestPartitionId(requestPartitionId) - .execute(() -> toResourceList(mySearchBuilder, pids, thePageBuilder)); + .execute(() -> toResourceList(mySearchBuilder, firstBatch, thePageBuilder)); long totalCountWanted = theToIndex - theFromIndex; long totalCountMatch = (int) retVal.stream().filter(t -> !isInclude(t)).count(); @@ -103,12 +110,15 @@ public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundl long remainingWanted = totalCountWanted - totalCountMatch; long fromIndex = theToIndex - remainingWanted; - List remaining = super.getResources((int) fromIndex, theToIndex, thePageBuilder); + ResponsePage.ResponsePageBuilder pageBuilder = new ResponsePage.ResponsePageBuilder(); + pageBuilder.setBundleProvider(this); + List remaining = super.getResources((int) fromIndex, theToIndex, pageBuilder); remaining.forEach(t -> { if (!existingIds.contains(t.getIdElement().getValue())) { retVal.add(t); } }); + thePageBuilder.combineWith(pageBuilder); } } ourLog.trace("Loaded resources to return"); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SynchronousSearchSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SynchronousSearchSvcImpl.java index 3627b72a5d4..89f77e51b14 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SynchronousSearchSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SynchronousSearchSvcImpl.java @@ -115,7 +115,7 @@ public class SynchronousSearchSvcImpl implements ISynchronousSearchSvc { .execute(() -> { // Load the results synchronously - final List pids = new ArrayList<>(); + List pids = new ArrayList<>(); Long count = 0L; if (wantCount) { @@ -145,8 +145,17 @@ public class SynchronousSearchSvcImpl implements ISynchronousSearchSvc { return bundleProvider; } + // if we have a count, we'll want to request + // additional resources + SearchParameterMap clonedParams = theParams.clone(); + Integer requestedCount = clonedParams.getCount(); + boolean hasACount = requestedCount != null; + if (hasACount) { + clonedParams.setCount(requestedCount.intValue() + 1); + } + try (IResultIterator resultIter = theSb.createQuery( - theParams, searchRuntimeDetails, theRequestDetails, theRequestPartitionId)) { + clonedParams, searchRuntimeDetails, theRequestDetails, theRequestPartitionId)) { while (resultIter.hasNext()) { pids.add(resultIter.next()); if (theLoadSynchronousUpTo != null && pids.size() >= theLoadSynchronousUpTo) { @@ -162,6 +171,15 @@ public class SynchronousSearchSvcImpl implements ISynchronousSearchSvc { throw new InternalErrorException(Msg.code(1164) + e); } + // truncate the list we retrieved - if needed + int receivedResourceCount = -1; + if (hasACount) { + // we want the accurate received resource count + receivedResourceCount = pids.size(); + int resourcesToReturn = Math.min(theParams.getCount(), pids.size()); + pids = pids.subList(0, resourcesToReturn); + } + JpaPreResourceAccessDetails accessDetails = new JpaPreResourceAccessDetails(pids, () -> theSb); HookParams params = new HookParams() .add(IPreResourceAccessDetails.class, accessDetails) @@ -228,6 +246,9 @@ public class SynchronousSearchSvcImpl implements ISynchronousSearchSvc { resources, theRequestDetails, myInterceptorBroadcaster); SimpleBundleProvider bundleProvider = new SimpleBundleProvider(resources); + if (hasACount) { + bundleProvider.setTotalResourcesRequestedReturned(receivedResourceCount); + } if (theParams.isOffsetQuery()) { bundleProvider.setCurrentPageOffset(theParams.getOffset()); bundleProvider.setCurrentPageSize(theParams.getCount()); diff --git a/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/search/SynchronousSearchSvcImplTest.java b/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/search/SynchronousSearchSvcImplTest.java index 5c9309b4369..456f42ee90a 100644 --- a/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/search/SynchronousSearchSvcImplTest.java +++ b/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/search/SynchronousSearchSvcImplTest.java @@ -42,14 +42,17 @@ public class SynchronousSearchSvcImplTest extends BaseSearchSvc { @Test public void testSynchronousSearch() { - when(mySearchBuilderFactory.newSearchBuilder(any(), any(), any())).thenReturn(mySearchBuilder); + when(mySearchBuilderFactory.newSearchBuilder(any(), any(), any())) + .thenReturn(mySearchBuilder); SearchParameterMap params = new SearchParameterMap(); List pids = createPidSequence(800); - when(mySearchBuilder.createQuery(same(params), any(), any(), nullable(RequestPartitionId.class))).thenReturn(new BaseSearchSvc.ResultIterator(pids.iterator())); + when(mySearchBuilder.createQuery(any(SearchParameterMap.class), any(), any(), nullable(RequestPartitionId.class))) + .thenReturn(new BaseSearchSvc.ResultIterator(pids.iterator())); - doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); + doAnswer(loadPids()).when(mySearchBuilder) + .loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); IBundleProvider result = mySynchronousSearchSvc.executeQuery( "Patient", params, RequestPartitionId.allPartitions()); assertNull(result.getUuid()); @@ -71,8 +74,8 @@ public class SynchronousSearchSvcImplTest extends BaseSearchSvc { params.setSearchTotalMode(SearchTotalModeEnum.ACCURATE); List pids = createPidSequence(30); - when(mySearchBuilder.createCountQuery(same(params), any(String.class),nullable(RequestDetails.class), nullable(RequestPartitionId.class))).thenReturn(20L); - when(mySearchBuilder.createQuery(same(params), any(), nullable(RequestDetails.class), nullable(RequestPartitionId.class))).thenReturn(new BaseSearchSvc.ResultIterator(pids.subList(10, 20).iterator())); + when(mySearchBuilder.createCountQuery(any(SearchParameterMap.class), any(String.class),nullable(RequestDetails.class), nullable(RequestPartitionId.class))).thenReturn(20L); + when(mySearchBuilder.createQuery(any(SearchParameterMap.class), any(), nullable(RequestDetails.class), nullable(RequestPartitionId.class))).thenReturn(new BaseSearchSvc.ResultIterator(pids.subList(10, 20).iterator())); doAnswer(loadPids()).when(mySearchBuilder).loadResourcesByPid(any(Collection.class), any(Collection.class), any(List.class), anyBoolean(), any()); @@ -92,7 +95,8 @@ public class SynchronousSearchSvcImplTest extends BaseSearchSvc { params.setLoadSynchronousUpTo(100); List pids = createPidSequence(800); - when(mySearchBuilder.createQuery(same(params), any(), nullable(RequestDetails.class), nullable(RequestPartitionId.class))).thenReturn(new BaseSearchSvc.ResultIterator(pids.iterator())); + when(mySearchBuilder.createQuery(any(SearchParameterMap.class), any(), nullable(RequestDetails.class), nullable(RequestPartitionId.class))) + .thenReturn(new BaseSearchSvc.ResultIterator(pids.iterator())); pids = createPidSequence(110); List finalPids = pids; diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ConsentEventsDaoR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ConsentEventsDaoR4Test.java index 0c1e49496f6..b5493dcebd5 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ConsentEventsDaoR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ConsentEventsDaoR4Test.java @@ -29,11 +29,11 @@ import org.slf4j.LoggerFactory; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; -import javax.servlet.ServletException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.leftPad; @@ -54,7 +54,7 @@ public class ConsentEventsDaoR4Test extends BaseJpaR4SystemTest { private List myPatientIds; private List myObservationIdsOddOnly; private List myObservationIdsEvenOnly; - private List myObservationIdsWithVersions; + private List myObservationIdsWithoutVersions; private List myPatientIdsEvenOnly; @AfterEach @@ -64,13 +64,16 @@ public class ConsentEventsDaoR4Test extends BaseJpaR4SystemTest { } @BeforeEach - public void before() throws ServletException { + @Override + public void beforeInitMocks() throws Exception { + super.beforeInitMocks(); RestfulServer restfulServer = new RestfulServer(); restfulServer.setPagingProvider(myPagingProvider); when(mySrd.getServer()).thenReturn(restfulServer); myStorageSettings.setSearchPreFetchThresholds(Arrays.asList(20, 50, 190)); + restfulServer.setDefaultPageSize(null); } @Test @@ -147,6 +150,7 @@ public class ConsentEventsDaoR4Test extends BaseJpaR4SystemTest { @Test public void testSearchAndBlockSome_LoadSynchronous() { + // setup create50Observations(); AtomicInteger hitCount = new AtomicInteger(0); @@ -281,6 +285,7 @@ public class ConsentEventsDaoR4Test extends BaseJpaR4SystemTest { @Test public void testSearchAndBlockSomeOnIncludes_LoadSynchronous() { + // setup create50Observations(); AtomicInteger hitCount = new AtomicInteger(0); @@ -328,9 +333,8 @@ public class ConsentEventsDaoR4Test extends BaseJpaR4SystemTest { * returned results because we create it then update it in create50Observations() */ assertEquals(1, hitCount.get()); - assertEquals(myObservationIdsWithVersions.subList(90, myObservationIdsWithVersions.size()), sort(interceptedResourceIds)); + assertEquals(sort(myObservationIdsWithoutVersions.subList(90, myObservationIdsWithoutVersions.size())), sort(interceptedResourceIds)); returnedIdValues.forEach(t -> assertTrue(new IdType(t).getIdPartAsLong() % 2 == 0)); - } @Test @@ -363,7 +367,7 @@ public class ConsentEventsDaoR4Test extends BaseJpaR4SystemTest { private void create50Observations() { myPatientIds = new ArrayList<>(); myObservationIds = new ArrayList<>(); - myObservationIdsWithVersions = new ArrayList<>(); + myObservationIdsWithoutVersions = new ArrayList<>(); Patient p = new Patient(); p.setActive(true); @@ -383,9 +387,9 @@ public class ConsentEventsDaoR4Test extends BaseJpaR4SystemTest { final Observation obs1 = new Observation(); obs1.setStatus(Observation.ObservationStatus.FINAL); obs1.addIdentifier().setSystem("urn:system").setValue("I" + leftPad("" + i, 5, '0')); - IIdType obs1id = myObservationDao.create(obs1).getId().toUnqualifiedVersionless(); + IIdType obs1id = myObservationDao.create(obs1).getId(); myObservationIds.add(obs1id.toUnqualifiedVersionless().getValue()); - myObservationIdsWithVersions.add(obs1id.toUnqualifiedVersionless().getValue()); + myObservationIdsWithoutVersions.add(obs1id.toUnqualifiedVersionless().getValue()); obs1.setId(obs1id); if (obs1id.getIdPartAsLong() % 2 == 0) { @@ -394,7 +398,7 @@ public class ConsentEventsDaoR4Test extends BaseJpaR4SystemTest { obs1.getSubject().setReference(oddPid); } myObservationDao.update(obs1); - myObservationIdsWithVersions.add(obs1id.toUnqualifiedVersionless().getValue()); + myObservationIdsWithoutVersions.add(obs1id.toUnqualifiedVersionless().getValue()); } @@ -483,14 +487,24 @@ public class ConsentEventsDaoR4Test extends BaseJpaR4SystemTest { } } - private static List sort(List... theLists) { + private List sort(List... theLists) { + return sort(id -> { + String idParsed = id.substring(id.indexOf("/") + 1); + if (idParsed.contains("/_history")) { + idParsed = idParsed.substring(0, idParsed.indexOf("/")); + } + return Long.parseLong(idParsed); + }, theLists); + } + + private List sort(Function theParser, List... theLists) { ArrayList retVal = new ArrayList<>(); for (List next : theLists) { retVal.addAll(next); } retVal.sort((o0, o1) -> { - long i0 = Long.parseLong(o0.substring(o0.indexOf('/') + 1)); - long i1 = Long.parseLong(o1.substring(o1.indexOf('/') + 1)); + long i0 = theParser.apply(o0); + long i1 = theParser.apply(o1); return (int) (i0 - i1); }); return retVal; diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java index e1a535b678b..457ba5e943d 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java @@ -114,6 +114,7 @@ import static org.hamcrest.Matchers.not; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.eq; @@ -1229,6 +1230,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test nextChunk.forEach(t -> foundIds.add(t.getIdElement().toUnqualifiedVersionless().getValue())); } + assertEquals(ids.size(), foundIds.size()); ids.sort(new ComparableComparator<>()); foundIds.sort(new ComparableComparator<>()); assertEquals(ids, foundIds); @@ -1327,7 +1329,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test myCaptureQueriesListener.logSelectQueries(); assertEquals(2, myCaptureQueriesListener.countSelectQueries()); assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0")); - assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("limit '5'")); + assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("limit '6'")); assertEquals(0, myCaptureQueriesListener.countInsertQueries()); assertEquals(0, myCaptureQueriesListener.countUpdateQueries()); assertEquals(0, myCaptureQueriesListener.countDeleteQueries()); @@ -1343,7 +1345,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test myCaptureQueriesListener.logSelectQueries(); assertEquals(2, myCaptureQueriesListener.countSelectQueries()); assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0")); - assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("limit '5'")); + assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("limit '6'")); assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("offset '5'")); assertEquals(0, myCaptureQueriesListener.countInsertQueries()); assertEquals(0, myCaptureQueriesListener.countUpdateQueries()); @@ -1351,22 +1353,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test assertEquals(1, myCaptureQueriesListener.countCommits()); assertEquals(0, myCaptureQueriesListener.countRollbacks()); - assertThat(outcome.getLink("next").getUrl(), containsString("Patient?_count=5&_offset=10&active=true")); - - // Third page (no results) - - myCaptureQueriesListener.clear(); - outcome = myClient.search().forResource("Patient").where(Patient.ACTIVE.exactly().code("true")).offset(10).count(5).returnBundle(Bundle.class).execute(); - assertThat(toUnqualifiedVersionlessIdValues(outcome).toString(), toUnqualifiedVersionlessIdValues(outcome), empty()); - myCaptureQueriesListener.logSelectQueries(); - assertEquals(1, myCaptureQueriesListener.countSelectQueries()); - assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0")); - assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("limit '5'")); - assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("offset '10'")); - assertEquals(0, myCaptureQueriesListener.countInsertQueries()); - assertEquals(0, myCaptureQueriesListener.countUpdateQueries()); - assertEquals(0, myCaptureQueriesListener.countDeleteQueries()); - + assertNull(outcome.getLink("next")); } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/interceptor/ForceOffsetSearchModeInterceptorTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/interceptor/ForceOffsetSearchModeInterceptorTest.java index 716d631232b..945f6f19472 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/interceptor/ForceOffsetSearchModeInterceptorTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/interceptor/ForceOffsetSearchModeInterceptorTest.java @@ -13,10 +13,7 @@ import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.emptyOrNullString; import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -66,7 +63,7 @@ public class ForceOffsetSearchModeInterceptorTest extends BaseResourceProviderR4 myCaptureQueriesListener.logSelectQueries(); assertEquals(2, myCaptureQueriesListener.countSelectQueries()); assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0")); - assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("limit '5'")); + assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("limit '6'")); assertEquals(0, myCaptureQueriesListener.countInsertQueries()); assertEquals(0, myCaptureQueriesListener.countUpdateQueries()); assertEquals(0, myCaptureQueriesListener.countDeleteQueries()); @@ -91,7 +88,7 @@ public class ForceOffsetSearchModeInterceptorTest extends BaseResourceProviderR4 myCaptureQueriesListener.logSelectQueries(); assertEquals(2, myCaptureQueriesListener.countSelectQueries()); assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0")); - assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("limit '5'")); + assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("limit '6'")); assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("offset '5'")); assertEquals(0, myCaptureQueriesListener.countInsertQueries()); assertEquals(0, myCaptureQueriesListener.countUpdateQueries()); @@ -99,31 +96,7 @@ public class ForceOffsetSearchModeInterceptorTest extends BaseResourceProviderR4 assertEquals(1, myCaptureQueriesListener.countCommits()); assertEquals(0, myCaptureQueriesListener.countRollbacks()); - assertThat(outcome.getLink("next").getUrl(), containsString("Patient?_count=5&_offset=10&active=true")); - - // Third page (no results) - - myCaptureQueriesListener.clear(); - Bundle outcome3 = myClient - .search() - .forResource("Patient") - .where(Patient.ACTIVE.exactly().code("true")) - .offset(10) - .count(5) - .returnBundle(Bundle.class) - .execute(); - assertThat(toUnqualifiedVersionlessIdValues(outcome3).toString(), toUnqualifiedVersionlessIdValues(outcome3), empty()); - myCaptureQueriesListener.logSelectQueries(); - assertEquals(1, myCaptureQueriesListener.countSelectQueries()); - assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0")); - assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("limit '5'")); - assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("offset '10'")); - assertEquals(0, myCaptureQueriesListener.countInsertQueries()); - assertEquals(0, myCaptureQueriesListener.countUpdateQueries()); - assertEquals(0, myCaptureQueriesListener.countDeleteQueries()); - - assertNull(outcome3.getLink("next"), () -> outcome3.getLink("next").getUrl()); - + assertNull(outcome.getLink("next")); } @Test @@ -148,11 +121,7 @@ public class ForceOffsetSearchModeInterceptorTest extends BaseResourceProviderR4 assertThat(secondPageBundle.getEntry(), hasSize(5)); - Bundle thirdPageBundle = myClient.loadPage().next(secondPageBundle).execute(); - - assertThat(thirdPageBundle.getEntry(), hasSize(0)); - assertNull(thirdPageBundle.getLink("next"), () -> thirdPageBundle.getLink("next").getUrl()); - + assertNull(secondPageBundle.getLink("next")); } @@ -180,7 +149,7 @@ public class ForceOffsetSearchModeInterceptorTest extends BaseResourceProviderR4 myCaptureQueriesListener.logSelectQueries(); assertEquals(2, myCaptureQueriesListener.countSelectQueries()); assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0")); - assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("limit '7'")); + assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("limit '8'")); assertEquals(0, myCaptureQueriesListener.countInsertQueries()); assertEquals(0, myCaptureQueriesListener.countUpdateQueries()); assertEquals(0, myCaptureQueriesListener.countDeleteQueries()); @@ -203,7 +172,7 @@ public class ForceOffsetSearchModeInterceptorTest extends BaseResourceProviderR4 myCaptureQueriesListener.logSelectQueries(); assertEquals(2, myCaptureQueriesListener.countSelectQueries()); assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("SELECT t0.RES_ID FROM HFJ_SPIDX_TOKEN t0")); - assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("limit '7'")); + assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false), containsString("limit '8'")); assertEquals(0, myCaptureQueriesListener.countInsertQueries()); assertEquals(0, myCaptureQueriesListener.countUpdateQueries()); assertEquals(0, myCaptureQueriesListener.countDeleteQueries()); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderCustomSearchParamR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderCustomSearchParamR4Test.java index 22f2e3e19df..d5ac558d272 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderCustomSearchParamR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderCustomSearchParamR4Test.java @@ -484,7 +484,6 @@ public class ResourceProviderCustomSearchParamR4Test extends BaseResourceProvide */ @Test public void testCustomParameterMatchingManyValues() { - List found = new ArrayList<>(); class Interceptor { @@ -496,7 +495,6 @@ public class ResourceProviderCustomSearchParamR4Test extends BaseResourceProvide Interceptor interceptor = new Interceptor(); myInterceptorRegistry.registerInterceptor(interceptor); try { - int textIndex = 0; List ids = new ArrayList<>(); for (int i = 0; i < 200; i++) { @@ -549,9 +547,8 @@ public class ResourceProviderCustomSearchParamR4Test extends BaseResourceProvide ourLog.info("Found: {}", found); runInTransaction(() -> { - - List currentResults = myEntityManager.createNativeQuery("select distinct resourceta0_.RES_ID as col_0_0_ from HFJ_RESOURCE resourceta0_ left outer join HFJ_SPIDX_STRING myparamsst1_ on resourceta0_.RES_ID=myparamsst1_.RES_ID where myparamsst1_.HASH_NORM_PREFIX='5901791607832193956' and (myparamsst1_.SP_VALUE_NORMALIZED like 'SECTION%') limit '500'").getResultList(); - List currentResources = myEntityManager.createNativeQuery("select resourceta0_.RES_ID as col_0_0_ from HFJ_RESOURCE resourceta0_").getResultList(); + List currentResults = myEntityManager.createNativeQuery("select distinct resourceta0_.RES_ID as col_0_0_ from HFJ_RESOURCE resourceta0_ left outer join HFJ_SPIDX_STRING myparamsst1_ on resourceta0_.RES_ID=myparamsst1_.RES_ID where myparamsst1_.HASH_NORM_PREFIX='5901791607832193956' and (myparamsst1_.SP_VALUE_NORMALIZED like 'SECTION%') limit '500'").getResultList(); + List currentResources = myEntityManager.createNativeQuery("select resourceta0_.RES_ID as col_0_0_ from HFJ_RESOURCE resourceta0_").getResultList(); List searches = mySearchEntityDao.findAll(); assertEquals(1, searches.size()); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4EverythingTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4EverythingTest.java index ea08b3b0b15..171120f54cb 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4EverythingTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4EverythingTest.java @@ -1012,7 +1012,6 @@ public class ResourceProviderR4EverythingTest extends BaseResourceProviderR4Test assertThat(ids, containsInAnyOrder("Patient/FOO", "Observation/BAZ")); } - @Test public void testPagingOverEverythingSet() throws InterruptedException { Patient p = new Patient(); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java index f2cb94da312..722cc8ae8e1 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java @@ -25,11 +25,13 @@ import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.StrictErrorHandler; +import ca.uhn.fhir.rest.api.CacheControlDirective; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.PreferReturnEnum; import ca.uhn.fhir.rest.api.SearchTotalModeEnum; import ca.uhn.fhir.rest.api.SummaryEnum; +import ca.uhn.fhir.rest.api.server.IRestfulServer; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.client.apache.ResourceEntity; import ca.uhn.fhir.rest.client.api.IClientInterceptor; @@ -42,6 +44,7 @@ import ca.uhn.fhir.rest.gclient.NumberClientParam; import ca.uhn.fhir.rest.gclient.StringClientParam; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.ParamPrefixEnum; +import ca.uhn.fhir.rest.server.IPagingProvider; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; @@ -159,8 +162,10 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Spy; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.util.AopTestUtils; import org.springframework.transaction.TransactionStatus; @@ -220,6 +225,9 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; @SuppressWarnings("Duplicates") public class ResourceProviderR4Test extends BaseResourceProviderR4Test { @@ -255,6 +263,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { myStorageSettings.setUpdateWithHistoryRewriteEnabled(false); myStorageSettings.setPreserveRequestIdInResourceBody(false); + when(myPagingProvider.canStoreSearchResults()) + .thenCallRealMethod(); } @BeforeEach @@ -2718,6 +2728,90 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertEquals(total + 1, ids.size()); } + + @ParameterizedTest + @CsvSource({ + "true,19,10", + "false,19,10", + "true,20,0", + "false,20,0" + }) + public void testPagingWithIncludesReturnsConsistentValues( + boolean theAllowStoringSearchResults, + int theResourceCount, + int theOrgCount + ) { + // setup + + // create resources + { + Coding tagCode = new Coding(); + tagCode.setCode("test"); + tagCode.setSystem("http://example.com"); + int orgCount = theOrgCount; + for (int i = 0; i < theResourceCount; i++) { + Task t = new Task(); + t.getMeta() + .addTag(tagCode); + t.setStatus(Task.TaskStatus.REQUESTED); + if (orgCount > 0) { + Organization org = new Organization(); + org.setName("ORG"); + IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless(); + + orgCount--; + t.getOwner().setReference(orgId.getValue()); + } + myTaskDao.create(t); + } + } + + // when + if (!theAllowStoringSearchResults) { + // we don't actually allow this in our current + // pagingProvider implementations (except for history). + // But we will test with it because our ResponsePage + // is what's under test here + when(myPagingProvider.canStoreSearchResults()) + .thenReturn(false); + } + + int requestedAmount = 10; + Bundle bundle = myClient + .search() + .byUrl("Task?_count=10&_tag=test&status=requested&_include=Task%3Aowner&_sort=status") + .returnBundle(Bundle.class) + .execute(); + int count = bundle.getEntry().size(); + assertFalse(bundle.getEntry().isEmpty()); + + String nextUrl = null; + do { + Bundle.BundleLinkComponent nextLink = bundle.getLink("next"); + if (nextLink != null) { + nextUrl = nextLink.getUrl(); + + // make sure we're always requesting 10 + assertTrue(nextUrl.contains(String.format("_count=%d", requestedAmount))); + + // get next batch + bundle = myClient.fetchResourceFromUrl(Bundle.class, nextUrl); + int received = bundle.getEntry().size(); + + // every next result should produce results + assertFalse(bundle.getEntry().isEmpty()); + count += received; + } else { + nextUrl = null; + } + } while (nextUrl != null); + + // verify + // we should receive all resources and linked resources + assertEquals(theResourceCount + theOrgCount, count); + } + + @Test public void testPagingWithIncludesReturnsConsistentValues() { // setup @@ -3204,7 +3298,11 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { }); myCaptureQueriesListener.logAllQueriesForCurrentThread(); - Bundle bundle = myClient.search().forResource("Patient").returnBundle(Bundle.class).execute(); + Bundle bundle = myClient + .search() + .forResource("Patient") + .returnBundle(Bundle.class) + .execute(); ourLog.debug("Result: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle)); assertEquals(2, bundle.getTotal()); assertEquals(1, bundle.getEntry().size()); diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java index 7bf94fc8e07..a0d630c9b12 100644 --- a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java @@ -215,7 +215,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; @ExtendWith(SpringExtension.class) -@ContextConfiguration(classes = {TestR4Config.class}) +@ContextConfiguration(classes = { + TestR4Config.class +}) public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuilder { public static final String MY_VALUE_SET = "my-value-set"; public static final String URL_MY_VALUE_SET = "http://example.com/my_value_set"; @@ -398,6 +400,7 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil @Autowired @Qualifier("myOrganizationAffiliationDaoR4") protected IFhirResourceDao myOrganizationAffiliationDao; + @Autowired protected DatabaseBackedPagingProvider myPagingProvider; @Autowired diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/config/TestHapiJpaConfig.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/config/TestHapiJpaConfig.java new file mode 100644 index 00000000000..5ce4a94655d --- /dev/null +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/config/TestHapiJpaConfig.java @@ -0,0 +1,21 @@ +package ca.uhn.fhir.jpa.test.config; + +import ca.uhn.fhir.jpa.config.HapiJpaConfig; +import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.mockito.Mockito.spy; + +/** + * This is a Test configuration class that allows spying underlying JpaConfigs beans + */ +@Configuration +public class TestHapiJpaConfig extends HapiJpaConfig { + + @Override + @Bean + public DatabaseBackedPagingProvider databaseBackedPagingProvider() { + return spy(super.databaseBackedPagingProvider()); + } +} diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/config/TestR4Config.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/config/TestR4Config.java index 98765d50c18..98942ba4d5a 100644 --- a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/config/TestR4Config.java @@ -24,7 +24,6 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.batch2.JpaBatch2Config; import ca.uhn.fhir.jpa.binary.api.IBinaryStorageSvc; import ca.uhn.fhir.jpa.binstore.MemoryBinaryStorageSvcImpl; -import ca.uhn.fhir.jpa.config.HapiJpaConfig; import ca.uhn.fhir.jpa.config.PackageLoaderConfig; import ca.uhn.fhir.jpa.config.r4.JpaR4Config; import ca.uhn.fhir.jpa.config.util.HapiEntityManagerFactoryUtil; @@ -65,7 +64,7 @@ import static org.junit.jupiter.api.Assertions.fail; @Import({ JpaR4Config.class, PackageLoaderConfig.class, - HapiJpaConfig.class, + TestHapiJpaConfig.class, TestJPAConfig.class, TestHSearchAddInConfig.DefaultLuceneHeap.class, JpaBatch2Config.class, diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/IBundleProvider.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/IBundleProvider.java index 5e1b96d63c7..77c02105714 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/IBundleProvider.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/IBundleProvider.java @@ -119,6 +119,7 @@ public interface IBundleProvider { * server's processing rules (e.g. _include'd resources, OperationOutcome, etc.). For example, * if the method is invoked with index 0,10 the method might return 10 search results, plus an * additional 20 resources which matched a client's _include specification. + *

*

* Note that if this bundle provider was loaded using a * page ID (i.e. via {@link ca.uhn.fhir.rest.server.IPagingProvider#retrieveResultList(RequestDetails, String, String)} diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/SimpleBundleProvider.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/SimpleBundleProvider.java index e9479ccd7ad..588f1909e7a 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/SimpleBundleProvider.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/SimpleBundleProvider.java @@ -43,6 +43,15 @@ public class SimpleBundleProvider implements IBundleProvider { private Integer myCurrentPageSize; private ResponsePage.ResponsePageBuilder myPageBuilder; + /** + * The actual number of resources we have tried to fetch. + * This value will only be populated if there is a + * _count query parameter provided. + * In which case, it will be the total number of resources + * we tried to fetch (should be _count + 1 for accurate paging) + */ + private int myTotalResourcesRequestedReturned = -1; + /** * Constructor */ @@ -144,6 +153,7 @@ public class SimpleBundleProvider implements IBundleProvider { @Override public List getResources( int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder) { + theResponsePageBuilder.setTotalRequestedResourcesFetched(myTotalResourcesRequestedReturned); return (List) myList.subList(Math.min(theFromIndex, myList.size()), Math.min(theToIndex, myList.size())); } @@ -153,6 +163,10 @@ public class SimpleBundleProvider implements IBundleProvider { return myUuid; } + public void setTotalResourcesRequestedReturned(int theAmount) { + myTotalResourcesRequestedReturned = theAmount; + } + /** * Defaults to null */ diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ResponseBundleBuilder.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ResponseBundleBuilder.java index 0d99af7ea91..b3815d66d45 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ResponseBundleBuilder.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ResponseBundleBuilder.java @@ -105,8 +105,11 @@ public class ResponseBundleBuilder { pageSize = pagingCalculatePageSize(requestedPage, server.getPagingProvider()); Integer size = bundleProvider.size(); - numToReturn = - (size == null) ? pageSize : Math.min(pageSize, size.intValue() - theResponseBundleRequest.offset); + if (size == null) { + numToReturn = pageSize; + } else { + numToReturn = Math.min(pageSize, size.intValue() - theResponseBundleRequest.offset); + } resourceList = pagingBuildResourceList(theResponseBundleRequest, bundleProvider, numToReturn, responsePageBuilder); @@ -252,6 +255,7 @@ public class ResponseBundleBuilder { RestfulServerUtils.prettyPrintResponse(server, theResponseBundleRequest.requestDetails), theResponseBundleRequest.bundleType); + // set self link retval.setSelf(theResponseBundleRequest.linkSelf); // determine if we are using offset / uncached pages diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ResponsePage.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ResponsePage.java index 5f797ffd435..7bfa3294938 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ResponsePage.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ResponsePage.java @@ -71,6 +71,16 @@ public class ResponsePage { * even though it will change number of resources returned. */ private final int myOmittedResourceCount; + /** + * This is the total count of requested resources + * (ie, non-omitted, non-_include'd resource count). + * We typically fetch (for offset queries) 1 more than + * we need so we know if there is an additional page + * to fetch. + * But this is determined by the implementers of + * IBundleProvider. + */ + private final int myTotalRequestedResourcesFetched; /** * The bundle provider. @@ -109,6 +119,7 @@ public class ResponsePage { int theNumToReturn, int theIncludedResourceCount, int theOmittedResourceCount, + int theTotalRequestedResourcesFetched, IBundleProvider theBundleProvider) { mySearchId = theSearchId; myResourceList = theResourceList; @@ -116,6 +127,7 @@ public class ResponsePage { myNumToReturn = theNumToReturn; myIncludedResourceCount = theIncludedResourceCount; myOmittedResourceCount = theOmittedResourceCount; + myTotalRequestedResourcesFetched = theTotalRequestedResourcesFetched; myBundleProvider = theBundleProvider; myNumTotalResults = myBundleProvider.size(); @@ -190,24 +202,16 @@ public class ResponsePage { return StringUtils.isNotBlank(myBundleProvider.getNextPageId()); case NONCACHED_OFFSET: if (myNumTotalResults == null) { - /* - * Having a null total results is synonymous with - * having a next link. Once our results are exhausted, - * we will always have a myNumTotalResults value. - * - * Alternatively, if _total=accurate is provided, - * we'll also have a myNumTotalResults value. - */ - return true; + if (hasNextPageWithoutKnowingTotal()) { + return true; + } } else if (myNumTotalResults > myNumToReturn + ObjectUtils.defaultIfNull(myRequestedPage.offset, 0)) { return true; } break; case SAVED_SEARCH: if (myNumTotalResults == null) { - if (myPageSize == myResourceList.size() + myOmittedResourceCount - myIncludedResourceCount) { - // if the size of the resource list - included resources + omitted resources == pagesize - // we have more pages + if (hasNextPageWithoutKnowingTotal()) { return true; } } else if (myResponseBundleRequest.offset + myNumToReturn < myNumTotalResults) { @@ -220,6 +224,53 @@ public class ResponsePage { return false; } + /** + * If myNumTotalResults is null, it typically means we don't + * have an accurate total. + * + * Ie, we're in the middle of a set of pages (of non-named page results), + * and _total=accurate was not passed. + * + * This typically always means that a + * 'next' link definitely exists. + * + * But there are cases where this might not be true: + * * the last page of a search that also has an _include + * query parameter where the total of resources + _include'd + * resources is > the page size expected to be returned. + * * the last page of a search that returns the exact number + * of resources requested + * + * In these case, we must check to see if the returned + * number of *requested* resources. + * If our bundleprovider has fetched > requested, + * we'll know that there are more resources already. + * But if it hasn't, we'll have to check pagesize compared to + * _include'd count, omitted count, and resource count. + */ + private boolean hasNextPageWithoutKnowingTotal() { + // if we have totalRequestedResource count, and it's not equal to pagesize, + // then we can use this, alone, to determine if there are more pages + if (myTotalRequestedResourcesFetched >= 0) { + if (myPageSize < myTotalRequestedResourcesFetched) { + return true; + } + } else { + // otherwise we'll try and determine if there are next links based on the following + // calculation: + // resourceList.size - included resources + omitted resources == pagesize + // -> we (most likely) have more resources + if (myPageSize == myResourceList.size() - myIncludedResourceCount + myOmittedResourceCount) { + ourLog.warn( + "Returning a next page based on calculated resource count." + + " This could be inaccurate if the exact number of resources were fetched is equal to the pagesize requested. " + + " Consider setting ResponseBundleBuilder.setTotalResourcesFetchedRequest after fetching resources."); + return true; + } + } + return false; + } + public void setNextPageIfNecessary(BundleLinks theLinks) { if (hasNextPage()) { String next; @@ -356,9 +407,10 @@ public class ResponsePage { private int myIncludedResourceCount; private int myOmittedResourceCount; private IBundleProvider myBundleProvider; + private int myTotalRequestedResourcesFetched = -1; - public ResponsePageBuilder setToOmittedResourceCount(int theOmittedResourcesCountToAdd) { - myOmittedResourceCount = theOmittedResourcesCountToAdd; + public ResponsePageBuilder setOmittedResourceCount(int theOmittedResourceCount) { + myOmittedResourceCount = theOmittedResourceCount; return this; } @@ -392,6 +444,36 @@ public class ResponsePage { return this; } + public ResponsePageBuilder setTotalRequestedResourcesFetched(int theTotalRequestedResourcesFetched) { + myTotalRequestedResourcesFetched = theTotalRequestedResourcesFetched; + return this; + } + + /** + * Combine this builder with a second buider. + * Useful if a second page is requested, but you do not wish to + * overwrite the current values. + * + * Will not replace searchId, nor IBundleProvider (which should be + * the exact same for any subsequent searches anyways). + * + * Will also not copy pageSize nor numToReturn, as these should be + * the same for any single search result set. + * + * @param theSecondBuilder - a second builder (cannot be this one) + */ + public void combineWith(ResponsePageBuilder theSecondBuilder) { + assert theSecondBuilder != this; // don't want to combine with itself + + if (myTotalRequestedResourcesFetched != -1 && theSecondBuilder.myTotalRequestedResourcesFetched != -1) { + myTotalRequestedResourcesFetched += theSecondBuilder.myTotalRequestedResourcesFetched; + } + + // primitives can always be added + myIncludedResourceCount += theSecondBuilder.myIncludedResourceCount; + myOmittedResourceCount += theSecondBuilder.myOmittedResourceCount; + } + public ResponsePage build() { return new ResponsePage( mySearchId, // search id @@ -400,6 +482,7 @@ public class ResponsePage { myNumToReturn, // num to return myIncludedResourceCount, // included count myOmittedResourceCount, // omitted resources + myTotalRequestedResourcesFetched, // total count of requested resources myBundleProvider // the bundle provider ); } diff --git a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/api/server/method/ResponsePageTest.java b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/api/server/method/ResponsePageTest.java index 0546cdd8cd5..b5e00d6839b 100644 --- a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/api/server/method/ResponsePageTest.java +++ b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/api/server/method/ResponsePageTest.java @@ -161,17 +161,24 @@ public class ResponsePageTest { */ @ParameterizedTest @CsvSource({ - "true,false,true", - "true,true,true", - "false,false,false", - "false,true,false", - "false,false,true", - "false,true,true" + "true,false,true,true", + "true,true,true,true", + "false,false,false,true", + "false,true,false,true", + "false,false,true,true", + "false,true,true,true", + "true,false,true,false", + "true,true,true,false", + "false,false,false,false", + "false,true,false,false", + "false,false,true,false", + "false,true,true,false" }) public void nonCachedOffsetPaging_setsNextPreviousLinks_test( boolean theNumTotalResultsIsNull, boolean theHasPreviousBoolean, - boolean theHasNextBoolean + boolean theHasNextBoolean, + boolean theHasTotalRequestedCountBool ) { // setup myBundleBuilder @@ -193,6 +200,11 @@ public class ResponsePageTest { } else { when(myBundleProvider.size()) .thenReturn(null); + if (theHasTotalRequestedCountBool) { + myBundleBuilder.setTotalRequestedResourcesFetched(11); // 1 more than pagesize + } else { + myBundleBuilder.setPageSize(10); + } } RequestedPage requestedPage = new RequestedPage( @@ -215,19 +227,28 @@ public class ResponsePageTest { @ParameterizedTest @CsvSource({ - "true,false,false", - "true,true,false", - "true,false,true", - "true,true,true", - "false,false,false", - "false,true,false", - "false,false,true", - "false,true,true" + "true,false,false,true", + "true,true,false,true", + "true,false,true,true", + "true,true,true,true", + "false,false,false,true", + "false,true,false,true", + "false,false,true,true", + "false,true,true,true", + "true,false,false,false", + "true,true,false,false", + "true,false,true,false", + "true,true,true,false", + "false,false,false,false", + "false,true,false,false", + "false,false,true,false", + "false,true,true,false" }) public void savedSearch_setsNextPreviousLinks_test( boolean theNumTotalResultsIsNull, boolean theHasPreviousBoolean, - boolean theHasNextBoolean + boolean theHasNextBoolean, + boolean theHasTotalRequestedFetched ) { // setup int pageSize = myList.size(); @@ -255,6 +276,12 @@ public class ResponsePageTest { if (!theHasNextBoolean) { myBundleBuilder.setNumToReturn(pageSize + offset + includeResourceCount); } + } else if (theHasTotalRequestedFetched) { + if (theHasNextBoolean) { + myBundleBuilder.setTotalRequestedResourcesFetched(pageSize + 1); // 1 more than page size + } else { + myBundleBuilder.setTotalRequestedResourcesFetched(pageSize); + } } // when diff --git a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/imprt/BulkDataImportProvider.java b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/imprt/BulkDataImportProvider.java index c75342c414b..6e2396a6401 100644 --- a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/imprt/BulkDataImportProvider.java +++ b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/imprt/BulkDataImportProvider.java @@ -76,13 +76,10 @@ public class BulkDataImportProvider { public static final String PARAM_INPUT_TYPE = "type"; private static final Logger ourLog = LoggerFactory.getLogger(BulkDataImportProvider.class); - @Autowired private IJobCoordinator myJobCoordinator; - @Autowired private FhirContext myFhirCtx; - @Autowired private IRequestPartitionHelperSvc myRequestPartitionHelperService; private volatile List myResourceTypeOrder; @@ -94,14 +91,17 @@ public class BulkDataImportProvider { super(); } + @Autowired public void setJobCoordinator(IJobCoordinator theJobCoordinator) { myJobCoordinator = theJobCoordinator; } + @Autowired public void setFhirContext(FhirContext theCtx) { myFhirCtx = theCtx; } + @Autowired public void setRequestPartitionHelperService(IRequestPartitionHelperSvc theRequestPartitionHelperSvc) { myRequestPartitionHelperService = theRequestPartitionHelperSvc; } From 469873aff7a573d9d894bc4e8ff2f6118bdd04b8 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Wed, 29 Nov 2023 09:16:41 -0500 Subject: [PATCH 04/14] Add migrator to uhnfhirtest (#5508) * Add migrator to uhnfhirtest * Fix spotless * Test fix --- .../HapiFlywayMigrateDatabaseCommandTest.java | 10 ++-- .../tasks/HapiFhirJpaMigrationTasks.java | 49 ++++++++++-------- .../ca/uhn/fhirtest/config/CommonConfig.java | 6 +++ .../uhn/fhirtest/config/TestAuditConfig.java | 2 +- .../uhn/fhirtest/config/TestDstu2Config.java | 2 +- .../uhn/fhirtest/config/TestDstu3Config.java | 2 +- .../ca/uhn/fhirtest/config/TestR4BConfig.java | 2 +- .../ca/uhn/fhirtest/config/TestR4Config.java | 2 +- .../ca/uhn/fhirtest/config/TestR5Config.java | 2 +- .../migrate/FhirTestAutoMigrator.java | 51 +++++++++++++++++++ 10 files changed, 98 insertions(+), 30 deletions(-) create mode 100644 hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/migrate/FhirTestAutoMigrator.java diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HapiFlywayMigrateDatabaseCommandTest.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HapiFlywayMigrateDatabaseCommandTest.java index cebbf9c9a8b..31b74d0b5ab 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HapiFlywayMigrateDatabaseCommandTest.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/HapiFlywayMigrateDatabaseCommandTest.java @@ -5,11 +5,14 @@ import ca.uhn.fhir.jpa.migrate.JdbcUtils; import ca.uhn.fhir.jpa.migrate.SchemaMigrator; import ca.uhn.fhir.jpa.migrate.dao.HapiMigrationDao; import ca.uhn.fhir.jpa.migrate.entity.HapiMigrationEntity; +import ca.uhn.fhir.jpa.util.RandomTextUtils; import ca.uhn.fhir.system.HapiSystemProperties; import com.google.common.base.Charsets; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.core.JdbcTemplate; @@ -35,10 +38,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +@TestMethodOrder(MethodOrderer.MethodName.class) public class HapiFlywayMigrateDatabaseCommandTest { private static final Logger ourLog = LoggerFactory.getLogger(HapiFlywayMigrateDatabaseCommandTest.class); - public static final String DB_DIRECTORY = "target/h2_test"; + private final String myDbDirectory = "target/h2_test/" + RandomTextUtils.newSecureRandomAlphaNumericString(5); static { HapiSystemProperties.enableTestMode(); @@ -252,12 +256,12 @@ public class HapiFlywayMigrateDatabaseCommandTest { @Nonnull private File getLocation(String theDatabaseName) throws IOException { - File directory = new File(DB_DIRECTORY); + File directory = new File(myDbDirectory); if (directory.exists()) { FileUtils.deleteDirectory(directory); } - return new File(DB_DIRECTORY + "/" + theDatabaseName); + return new File(myDbDirectory + "/" + theDatabaseName); } private void seedDatabase340(DriverTypeEnum.ConnectionProperties theConnectionProperties) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java index a0f718dceb8..f480b23ed6e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java @@ -977,27 +977,34 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { // Ugh. Only oracle supports using IDX_TAG_DEF_TP_CD_SYS to enforce this constraint. The others will // create another index. // For Sql Server, should change the index to be unique with include columns. Do this in 6.1 - tagTable.dropIndex("20220429.8", "IDX_TAGDEF_TYPESYSCODE"); - Map addTagDefConstraint = new HashMap<>(); - addTagDefConstraint.put( - DriverTypeEnum.H2_EMBEDDED, - "ALTER TABLE HFJ_TAG_DEF ADD CONSTRAINT IDX_TAGDEF_TYPESYSCODE UNIQUE (TAG_TYPE, TAG_CODE, TAG_SYSTEM)"); - addTagDefConstraint.put( - DriverTypeEnum.MARIADB_10_1, - "ALTER TABLE HFJ_TAG_DEF ADD CONSTRAINT IDX_TAGDEF_TYPESYSCODE UNIQUE (TAG_TYPE, TAG_CODE, TAG_SYSTEM)"); - addTagDefConstraint.put( - DriverTypeEnum.MSSQL_2012, - "ALTER TABLE HFJ_TAG_DEF ADD CONSTRAINT IDX_TAGDEF_TYPESYSCODE UNIQUE (TAG_TYPE, TAG_CODE, TAG_SYSTEM)"); - addTagDefConstraint.put( - DriverTypeEnum.MYSQL_5_7, - "ALTER TABLE HFJ_TAG_DEF ADD CONSTRAINT IDX_TAGDEF_TYPESYSCODE UNIQUE (TAG_TYPE, TAG_CODE, TAG_SYSTEM)"); - addTagDefConstraint.put( - DriverTypeEnum.ORACLE_12C, - "ALTER TABLE HFJ_TAG_DEF ADD CONSTRAINT IDX_TAGDEF_TYPESYSCODE UNIQUE (TAG_TYPE, TAG_CODE, TAG_SYSTEM)"); - addTagDefConstraint.put( - DriverTypeEnum.POSTGRES_9_4, - "ALTER TABLE HFJ_TAG_DEF ADD CONSTRAINT IDX_TAGDEF_TYPESYSCODE UNIQUE (TAG_TYPE, TAG_CODE, TAG_SYSTEM)"); - version.executeRawSql("20220429.9", addTagDefConstraint); + // tagTable.dropIndex("20220429.8", "IDX_TAGDEF_TYPESYSCODE"); + // Map addTagDefConstraint = new HashMap<>(); + // addTagDefConstraint.put( + // DriverTypeEnum.H2_EMBEDDED, + // "ALTER TABLE HFJ_TAG_DEF ADD CONSTRAINT IDX_TAGDEF_TYPESYSCODE UNIQUE (TAG_TYPE, TAG_CODE, + // TAG_SYSTEM)"); + // addTagDefConstraint.put( + // DriverTypeEnum.MARIADB_10_1, + // "ALTER TABLE HFJ_TAG_DEF ADD CONSTRAINT IDX_TAGDEF_TYPESYSCODE UNIQUE (TAG_TYPE, TAG_CODE, + // TAG_SYSTEM)"); + // addTagDefConstraint.put( + // DriverTypeEnum.MSSQL_2012, + // "ALTER TABLE HFJ_TAG_DEF ADD CONSTRAINT IDX_TAGDEF_TYPESYSCODE UNIQUE (TAG_TYPE, TAG_CODE, + // TAG_SYSTEM)"); + // addTagDefConstraint.put( + // DriverTypeEnum.MYSQL_5_7, + // "ALTER TABLE HFJ_TAG_DEF ADD CONSTRAINT IDX_TAGDEF_TYPESYSCODE UNIQUE (TAG_TYPE, TAG_CODE, + // TAG_SYSTEM)"); + // addTagDefConstraint.put( + // DriverTypeEnum.ORACLE_12C, + // "ALTER TABLE HFJ_TAG_DEF ADD CONSTRAINT IDX_TAGDEF_TYPESYSCODE UNIQUE (TAG_TYPE, TAG_CODE, + // TAG_SYSTEM)"); + // addTagDefConstraint.put( + // DriverTypeEnum.POSTGRES_9_4, + // "ALTER TABLE HFJ_TAG_DEF ADD CONSTRAINT IDX_TAGDEF_TYPESYSCODE UNIQUE (TAG_TYPE, TAG_CODE, + // TAG_SYSTEM)"); + // version.executeRawSql("20220429.9", addTagDefConstraint); + version.addNop("20220429.9"); } // Fix for https://github.com/hapifhir/hapi-fhir-jpaserver-starter/issues/328 diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/CommonConfig.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/CommonConfig.java index b28a9367462..1d84422e1a2 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/CommonConfig.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/CommonConfig.java @@ -23,6 +23,7 @@ import ca.uhn.fhir.storage.interceptor.balp.IBalpAuditEventSink; import ca.uhn.fhirtest.ScheduledSubscriptionDeleter; import ca.uhn.fhirtest.interceptor.AnalyticsInterceptor; import ca.uhn.fhirtest.joke.HolyFooCowInterceptor; +import ca.uhn.fhirtest.migrate.FhirTestAutoMigrator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -131,6 +132,11 @@ public class CommonConfig { return new FhirTestBalpAuditContextServices(); } + @Bean + public FhirTestAutoMigrator migrator() { + return new FhirTestAutoMigrator(); + } + public static boolean isLocalTestMode() { return "true".equalsIgnoreCase(System.getProperty("testmode.local")); } diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestAuditConfig.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestAuditConfig.java index bd6c6690f16..03e838b3296 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestAuditConfig.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestAuditConfig.java @@ -120,7 +120,7 @@ public class TestAuditConfig { } extraProperties.put("hibernate.format_sql", "false"); extraProperties.put("hibernate.show_sql", "false"); - extraProperties.put("hibernate.hbm2ddl.auto", "update"); + extraProperties.put("hibernate.hbm2ddl.auto", "none"); extraProperties.put("hibernate.jdbc.batch_size", "20"); extraProperties.put("hibernate.cache.use_query_cache", "false"); extraProperties.put("hibernate.cache.use_second_level_cache", "false"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java index 7d957240c90..a3e4f203b2a 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java @@ -138,7 +138,7 @@ public class TestDstu2Config { } extraProperties.put("hibernate.format_sql", "false"); extraProperties.put("hibernate.show_sql", "false"); - extraProperties.put("hibernate.hbm2ddl.auto", "update"); + extraProperties.put("hibernate.hbm2ddl.auto", "none"); extraProperties.put("hibernate.jdbc.batch_size", "20"); extraProperties.put("hibernate.cache.use_query_cache", "false"); extraProperties.put("hibernate.cache.use_second_level_cache", "false"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java index da6b07ac91b..0e4e285b9a2 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java @@ -138,7 +138,7 @@ public class TestDstu3Config { } extraProperties.put("hibernate.format_sql", "false"); extraProperties.put("hibernate.show_sql", "false"); - extraProperties.put("hibernate.hbm2ddl.auto", "update"); + extraProperties.put("hibernate.hbm2ddl.auto", "none"); extraProperties.put("hibernate.jdbc.batch_size", "20"); extraProperties.put("hibernate.cache.use_query_cache", "false"); extraProperties.put("hibernate.cache.use_second_level_cache", "false"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4BConfig.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4BConfig.java index 454faeab90a..bafbdc2daf3 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4BConfig.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4BConfig.java @@ -136,7 +136,7 @@ public class TestR4BConfig { } extraProperties.put("hibernate.format_sql", "false"); extraProperties.put("hibernate.show_sql", "false"); - extraProperties.put("hibernate.hbm2ddl.auto", "update"); + extraProperties.put("hibernate.hbm2ddl.auto", "none"); extraProperties.put("hibernate.jdbc.batch_size", "20"); extraProperties.put("hibernate.cache.use_query_cache", "false"); extraProperties.put("hibernate.cache.use_second_level_cache", "false"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java index f95782ccc96..1c62b7a5225 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java @@ -139,7 +139,7 @@ public class TestR4Config { } extraProperties.put("hibernate.format_sql", "false"); extraProperties.put("hibernate.show_sql", "false"); - extraProperties.put("hibernate.hbm2ddl.auto", "update"); + extraProperties.put("hibernate.hbm2ddl.auto", "none"); extraProperties.put("hibernate.jdbc.batch_size", "20"); extraProperties.put("hibernate.cache.use_query_cache", "false"); extraProperties.put("hibernate.cache.use_second_level_cache", "false"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR5Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR5Config.java index 2870a925c32..e255f2627cb 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR5Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR5Config.java @@ -148,7 +148,7 @@ public class TestR5Config { } extraProperties.put("hibernate.format_sql", "false"); extraProperties.put("hibernate.show_sql", "false"); - extraProperties.put("hibernate.hbm2ddl.auto", "update"); + extraProperties.put("hibernate.hbm2ddl.auto", "none"); extraProperties.put("hibernate.jdbc.batch_size", "20"); extraProperties.put("hibernate.cache.use_query_cache", "false"); extraProperties.put("hibernate.cache.use_second_level_cache", "false"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/migrate/FhirTestAutoMigrator.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/migrate/FhirTestAutoMigrator.java new file mode 100644 index 00000000000..0de60267015 --- /dev/null +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/migrate/FhirTestAutoMigrator.java @@ -0,0 +1,51 @@ +package ca.uhn.fhirtest.migrate; + +import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; +import ca.uhn.fhir.jpa.migrate.HapiMigrationStorageSvc; +import ca.uhn.fhir.jpa.migrate.MigrationTaskList; +import ca.uhn.fhir.jpa.migrate.SchemaMigrator; +import ca.uhn.fhir.jpa.migrate.dao.HapiMigrationDao; +import ca.uhn.fhir.jpa.migrate.tasks.HapiFhirJpaMigrationTasks; +import ca.uhn.fhir.util.VersionEnum; +import ca.uhn.fhirtest.config.CommonConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Properties; +import java.util.Set; +import javax.annotation.PostConstruct; +import javax.sql.DataSource; + +public class FhirTestAutoMigrator { + + private static final Logger ourLog = LoggerFactory.getLogger(FhirTestAutoMigrator.class); + public static final String MIGRATION_TABLENAME = "MIGRATIONS"; + + @Autowired + private DataSource myDataSource; + + @PostConstruct + public void run() { + DriverTypeEnum driver; + if (CommonConfig.isLocalTestMode()) { + driver = DriverTypeEnum.H2_EMBEDDED; + } else { + driver = DriverTypeEnum.POSTGRES_9_4; + } + + HapiMigrationDao hapiMigrationDao = new HapiMigrationDao(myDataSource, driver, MIGRATION_TABLENAME); + HapiMigrationStorageSvc hapiMigrationStorageSvc = new HapiMigrationStorageSvc(hapiMigrationDao); + + MigrationTaskList tasks = new HapiFhirJpaMigrationTasks(Set.of()).getAllTasks(VersionEnum.values()); + + SchemaMigrator schemaMigrator = new SchemaMigrator( + "HAPI FHIR", MIGRATION_TABLENAME, myDataSource, new Properties(), tasks, hapiMigrationStorageSvc); + schemaMigrator.setDriverType(driver); + + ourLog.info("About to run migration..."); + schemaMigrator.createMigrationTableIfRequired(); + schemaMigrator.migrate(); + ourLog.info("Migration complete"); + } +} From e15d0430d0aaac352f4e574773f84375129ef42e Mon Sep 17 00:00:00 2001 From: Ken Stevens Date: Wed, 29 Nov 2023 18:15:28 -0500 Subject: [PATCH 05/14] Oracle create index migration recovery (#5511) --- .../5511-oracle-migration-create-index.yaml | 6 ++ .../jpa/migrate/taskdef/AddIndexTaskTest.java | 70 +++++++++++++++++++ .../jpa/migrate/taskdef/AddIndexTask.java | 13 +++- 3 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5511-oracle-migration-create-index.yaml create mode 100644 hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTaskTest.java diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5511-oracle-migration-create-index.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5511-oracle-migration-create-index.yaml new file mode 100644 index 00000000000..b9eda74e6d7 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5511-oracle-migration-create-index.yaml @@ -0,0 +1,6 @@ +--- +type: fix +issue: 5511 +title: "Previously, when creating an index as a part of a migration, if the index already existed with a different name +on Oracle, the migration would fail. This has been fixed so that the create index migration task now recovers with + a warning message if the index already exists with a different name." diff --git a/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTaskTest.java b/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTaskTest.java new file mode 100644 index 00000000000..9e7d5f1ce71 --- /dev/null +++ b/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTaskTest.java @@ -0,0 +1,70 @@ +package ca.uhn.fhir.jpa.migrate.taskdef; + +import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; +import ca.uhn.test.util.LogbackCaptureTestExtension; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.LoggingEvent; +import oracle.jdbc.OracleDatabaseException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.jdbc.UncategorizedSQLException; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.sql.DataSource; +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class AddIndexTaskTest { + @Mock + DriverTypeEnum.ConnectionProperties myConnectionProperties; + @Mock + DataSource myDataSource; + @Mock + TransactionTemplate myTransactionTemplate; + + @RegisterExtension + LogbackCaptureTestExtension myLogCapture = new LogbackCaptureTestExtension((Logger) AddIndexTask.ourLog, Level.WARN); + + + @Test + void testOracleException() throws SQLException { + final AddIndexTask task = new AddIndexTask("1", "1"); + task.setColumns(Collections.singletonList("COLUMN_NAME")); + task.setUnique(true); + task.setIndexName("INDEX_NAME"); + task.setConnectionProperties(myConnectionProperties); + + when(myConnectionProperties.getDataSource()).thenReturn(myDataSource); + when(myConnectionProperties.getTxTemplate()).thenReturn(myTransactionTemplate); + + final String sql = "create index INDEX_NAME on TABLE_NAME (COLUMN_NAME)"; + when(myTransactionTemplate.execute(any())) + .thenReturn(Collections.emptySet()) + .thenThrow(new UncategorizedSQLException("ORA-01408: such column list already indexed", sql, new SQLException("ORA-01408: such column list already indexed", "72000", 1408))); + + myLogCapture.clearEvents(); + + // Red-green: this used to throw an exception. Now it logs a warning. + task.execute(); + + List events = myLogCapture.getLogEvents(); + assertThat(events, hasSize(1)); + LoggingEvent event = (LoggingEvent) events.get(0); + assertThat(event.getFormattedMessage(), containsString("ORA-01408: such column list already indexed")); + } +} diff --git a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTask.java b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTask.java index b0d2ae6ccd4..d05b6784e06 100644 --- a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTask.java +++ b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/AddIndexTask.java @@ -37,7 +37,7 @@ import javax.annotation.Nonnull; public class AddIndexTask extends BaseTableTask { - private static final Logger ourLog = LoggerFactory.getLogger(AddIndexTask.class); + static final Logger ourLog = LoggerFactory.getLogger(AddIndexTask.class); private String myIndexName; private List myColumns; @@ -97,8 +97,15 @@ public class AddIndexTask extends BaseTableTask { try { executeSql(tableName, sql); } catch (Exception e) { - if (e.toString().contains("already exists")) { - ourLog.warn("Index {} already exists", myIndexName); + String message = e.toString(); + if (message.contains("already exists") + || + // The Oracle message is ORA-01408: such column list already indexed + // TODO KHS consider db-specific handling here that uses the error code instead of the message so + // this is language independent + // e.g. if the db is Oracle than checking e.getErrorCode() == 1408 should detect this case + message.contains("already indexed")) { + ourLog.warn("Index {} already exists: {}", myIndexName, e.getMessage()); } else { throw e; } From 8fbbaef0790aadb97fe92dba42ccc1d8da031ee5 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 30 Nov 2023 14:56:46 -0500 Subject: [PATCH 06/14] Add a new field to the CLIENT_RESPONSE pointcut in order to allow clients to mutate an HTTP response from the BaseClient. (#5488) * Add a new field to the CLIENT_RESPONSE pointcut in order to allow clients to mutate an HTTP response from the BaseClient. * Add FhirContext to ClientResponseContext. * Introduce a ModifiedStringApacheHttpResponse. Run spotless. * Remove TDOOs, add and update javadoc. * Spotless and copyright header. * Add changelog. * Unique error message code. * Only trigger the interceptor if the expected return type is a Bundle. * Fix spotless. * Code review feedback. * Spotless. * Bump to 6.11.3-SNAPSHOT --- hapi-deployable-pom/pom.xml | 2 +- hapi-fhir-android/pom.xml | 2 +- hapi-fhir-base/pom.xml | 2 +- .../ca/uhn/fhir/interceptor/api/Pointcut.java | 7 +- .../client/api/ClientResponseContext.java | 103 +++++++++++++ hapi-fhir-bom/pom.xml | 4 +- hapi-fhir-checkstyle/pom.xml | 2 +- hapi-fhir-cli/hapi-fhir-cli-api/pom.xml | 2 +- hapi-fhir-cli/hapi-fhir-cli-app/pom.xml | 2 +- hapi-fhir-cli/pom.xml | 2 +- hapi-fhir-client-okhttp/pom.xml | 2 +- hapi-fhir-client/pom.xml | 2 +- .../ModifiedStringApacheHttpResponse.java | 135 ++++++++++++++++++ .../uhn/fhir/rest/client/impl/BaseClient.java | 17 +++ hapi-fhir-converter/pom.xml | 2 +- hapi-fhir-dist/pom.xml | 2 +- hapi-fhir-docs/pom.xml | 2 +- ...ent-response-pointcut-mutate-response.yaml | 6 + hapi-fhir-jacoco/pom.xml | 2 +- hapi-fhir-jaxrsserver-base/pom.xml | 2 +- hapi-fhir-jpa/pom.xml | 2 +- hapi-fhir-jpaserver-base/pom.xml | 2 +- .../pom.xml | 2 +- hapi-fhir-jpaserver-hfql/pom.xml | 2 +- hapi-fhir-jpaserver-ips/pom.xml | 2 +- hapi-fhir-jpaserver-mdm/pom.xml | 2 +- hapi-fhir-jpaserver-model/pom.xml | 2 +- hapi-fhir-jpaserver-searchparam/pom.xml | 2 +- hapi-fhir-jpaserver-subscription/pom.xml | 2 +- hapi-fhir-jpaserver-test-dstu2/pom.xml | 2 +- hapi-fhir-jpaserver-test-dstu3/pom.xml | 2 +- hapi-fhir-jpaserver-test-r4/pom.xml | 2 +- hapi-fhir-jpaserver-test-r4b/pom.xml | 2 +- hapi-fhir-jpaserver-test-r5/pom.xml | 2 +- hapi-fhir-jpaserver-test-utilities/pom.xml | 2 +- hapi-fhir-jpaserver-uhnfhirtest/pom.xml | 2 +- hapi-fhir-server-cds-hooks/pom.xml | 2 +- hapi-fhir-server-mdm/pom.xml | 2 +- hapi-fhir-server-openapi/pom.xml | 2 +- hapi-fhir-server/pom.xml | 2 +- .../hapi-fhir-caching-api/pom.xml | 2 +- .../hapi-fhir-caching-caffeine/pom.xml | 4 +- .../hapi-fhir-caching-guava/pom.xml | 2 +- .../hapi-fhir-caching-testing/pom.xml | 2 +- hapi-fhir-serviceloaders/pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../hapi-fhir-spring-boot-samples/pom.xml | 2 +- .../hapi-fhir-spring-boot-starter/pom.xml | 2 +- hapi-fhir-spring-boot/pom.xml | 2 +- hapi-fhir-sql-migrate/pom.xml | 2 +- hapi-fhir-storage-batch2-jobs/pom.xml | 2 +- .../pom.xml | 2 +- hapi-fhir-storage-batch2/pom.xml | 2 +- hapi-fhir-storage-cr/pom.xml | 2 +- hapi-fhir-storage-mdm/pom.xml | 2 +- hapi-fhir-storage-test-utilities/pom.xml | 2 +- hapi-fhir-storage/pom.xml | 2 +- hapi-fhir-structures-dstu2.1/pom.xml | 2 +- hapi-fhir-structures-dstu2/pom.xml | 2 +- hapi-fhir-structures-dstu3/pom.xml | 2 +- hapi-fhir-structures-hl7org-dstu2/pom.xml | 2 +- hapi-fhir-structures-r4/pom.xml | 2 +- hapi-fhir-structures-r4b/pom.xml | 2 +- hapi-fhir-structures-r5/pom.xml | 2 +- hapi-fhir-test-utilities/pom.xml | 2 +- hapi-fhir-testpage-overlay/pom.xml | 2 +- .../pom.xml | 2 +- hapi-fhir-validation-resources-dstu2/pom.xml | 2 +- hapi-fhir-validation-resources-dstu3/pom.xml | 2 +- hapi-fhir-validation-resources-r4/pom.xml | 2 +- hapi-fhir-validation-resources-r4b/pom.xml | 2 +- hapi-fhir-validation-resources-r5/pom.xml | 2 +- hapi-fhir-validation/pom.xml | 2 +- hapi-tinder-plugin/pom.xml | 2 +- hapi-tinder-test/pom.xml | 2 +- pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- 82 files changed, 346 insertions(+), 80 deletions(-) create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/ClientResponseContext.java create mode 100644 hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/apache/ModifiedStringApacheHttpResponse.java create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5502-base-client-client-response-pointcut-mutate-response.yaml diff --git a/hapi-deployable-pom/pom.xml b/hapi-deployable-pom/pom.xml index 14ae49d44f6..25f29f27be9 100644 --- a/hapi-deployable-pom/pom.xml +++ b/hapi-deployable-pom/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-android/pom.xml b/hapi-fhir-android/pom.xml index c8d934ef99a..6ffe01c5578 100644 --- a/hapi-fhir-android/pom.xml +++ b/hapi-fhir-android/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml index 595f263a0df..e67b3027b07 100644 --- a/hapi-fhir-base/pom.xml +++ b/hapi-fhir-base/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java index 994ea9cd1cf..64b7267f63f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java @@ -93,6 +93,10 @@ public enum Pointcut implements IPointcut { *

  • * ca.uhn.fhir.rest.client.api.IRestfulClient - The client object making the request *
  • + *
  • + * ca.uhn.fhir.rest.client.api.ClientResponseContext - Contains an IHttpRequest, an IHttpResponse, and an IRestfulClient + * and also allows the client to mutate the contained IHttpResponse + *
  • * *

    * Hook methods must return void. @@ -101,7 +105,8 @@ public enum Pointcut implements IPointcut { void.class, "ca.uhn.fhir.rest.client.api.IHttpRequest", "ca.uhn.fhir.rest.client.api.IHttpResponse", - "ca.uhn.fhir.rest.client.api.IRestfulClient"), + "ca.uhn.fhir.rest.client.api.IRestfulClient", + "ca.uhn.fhir.rest.client.api.ClientResponseContext"), /** * Server Hook: diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/ClientResponseContext.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/ClientResponseContext.java new file mode 100644 index 00000000000..b0748bf2ab7 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/ClientResponseContext.java @@ -0,0 +1,103 @@ +/*- + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2023 Smile CDR, Inc. + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package ca.uhn.fhir.rest.client.api; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.interceptor.api.Pointcut; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import java.util.Objects; +import java.util.StringJoiner; + +/** + * Used to pass context to {@link Pointcut#CLIENT_RESPONSE}, including a mutable {@link IHttpResponse} + */ +public class ClientResponseContext { + private final IHttpRequest myHttpRequest; + private IHttpResponse myHttpResponse; + private final IRestfulClient myRestfulClient; + private final FhirContext myFhirContext; + private final Class myReturnType; + + public ClientResponseContext( + IHttpRequest myHttpRequest, + IHttpResponse theHttpResponse, + IRestfulClient myRestfulClient, + FhirContext theFhirContext, + Class theReturnType) { + this.myHttpRequest = myHttpRequest; + this.myHttpResponse = theHttpResponse; + this.myRestfulClient = myRestfulClient; + this.myFhirContext = theFhirContext; + this.myReturnType = theReturnType; + } + + public IHttpRequest getHttpRequest() { + return myHttpRequest; + } + + public IHttpResponse getHttpResponse() { + return myHttpResponse; + } + + public IRestfulClient getRestfulClient() { + return myRestfulClient; + } + + public FhirContext getFhirContext() { + return myFhirContext; + } + + public Class getReturnType() { + return myReturnType; + } + + public void setHttpResponse(IHttpResponse theHttpResponse) { + this.myHttpResponse = theHttpResponse; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ClientResponseContext that = (ClientResponseContext) o; + return Objects.equals(myHttpRequest, that.myHttpRequest) + && Objects.equals(myHttpResponse, that.myHttpResponse) + && Objects.equals(myRestfulClient, that.myRestfulClient) + && Objects.equals(myFhirContext, that.myFhirContext) + && Objects.equals(myReturnType, that.myReturnType); + } + + @Override + public int hashCode() { + return Objects.hash(myHttpRequest, myHttpResponse, myRestfulClient, myFhirContext, myReturnType); + } + + @Override + public String toString() { + return new StringJoiner(", ", ClientResponseContext.class.getSimpleName() + "[", "]") + .add("myHttpRequest=" + myHttpRequest) + .add("myHttpResponse=" + myHttpResponse) + .add("myRestfulClient=" + myRestfulClient) + .add("myFhirContext=" + myFhirContext) + .add("myReturnType=" + myReturnType) + .toString(); + } +} diff --git a/hapi-fhir-bom/pom.xml b/hapi-fhir-bom/pom.xml index d2c92c7102f..8b175d80acc 100644 --- a/hapi-fhir-bom/pom.xml +++ b/hapi-fhir-bom/pom.xml @@ -4,7 +4,7 @@ 4.0.0 ca.uhn.hapi.fhir hapi-fhir-bom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT pom HAPI FHIR BOM @@ -12,7 +12,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-checkstyle/pom.xml b/hapi-fhir-checkstyle/pom.xml index a09540f3cb5..9819c07f04a 100644 --- a/hapi-fhir-checkstyle/pom.xml +++ b/hapi-fhir-checkstyle/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml index 6cdef901a35..cfcaebca7a9 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml index 0dfb56390d6..0bbc80ea717 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir-cli - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-cli/pom.xml b/hapi-fhir-cli/pom.xml index d16c69b118e..86345f32a31 100644 --- a/hapi-fhir-cli/pom.xml +++ b/hapi-fhir-cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-client-okhttp/pom.xml b/hapi-fhir-client-okhttp/pom.xml index 5b7c57a6612..0d687f7b1b0 100644 --- a/hapi-fhir-client-okhttp/pom.xml +++ b/hapi-fhir-client-okhttp/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-client/pom.xml b/hapi-fhir-client/pom.xml index a0acac6d5bc..0f0e49a5f1b 100644 --- a/hapi-fhir-client/pom.xml +++ b/hapi-fhir-client/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/apache/ModifiedStringApacheHttpResponse.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/apache/ModifiedStringApacheHttpResponse.java new file mode 100644 index 00000000000..28ea8163668 --- /dev/null +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/apache/ModifiedStringApacheHttpResponse.java @@ -0,0 +1,135 @@ +/* + * #%L + * HAPI FHIR - Client Framework + * %% + * Copyright (C) 2014 - 2023 Smile CDR, Inc. + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package ca.uhn.fhir.rest.client.apache; + +import ca.uhn.fhir.i18n.Msg; +import ca.uhn.fhir.rest.client.api.IHttpResponse; +import ca.uhn.fhir.rest.client.impl.BaseHttpResponse; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.util.StopWatch; +import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.CloseableHttpResponse; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +/** + * Process a modified copy of an existing {@link IHttpResponse} with a String containing new content. + *

    + * Meant to be used with custom interceptors that need to hijack an existing IHttpResponse with new content. + */ +public class ModifiedStringApacheHttpResponse extends BaseHttpResponse implements IHttpResponse { + private static final org.slf4j.Logger ourLog = + org.slf4j.LoggerFactory.getLogger(ModifiedStringApacheHttpResponse.class); + private boolean myEntityBuffered = false; + private final String myNewContent; + private final IHttpResponse myOrigHttpResponse; + private byte[] myEntityBytes = null; + + public ModifiedStringApacheHttpResponse( + IHttpResponse theOrigHttpResponse, String theNewContent, StopWatch theResponseStopWatch) { + super(theResponseStopWatch); + myOrigHttpResponse = theOrigHttpResponse; + myNewContent = theNewContent; + } + + @Override + public void bufferEntity() throws IOException { + if (myEntityBuffered) { + return; + } + try (InputStream respEntity = readEntity()) { + if (respEntity != null) { + try { + myEntityBytes = IOUtils.toByteArray(respEntity); + } catch (IllegalStateException exception) { + throw new InternalErrorException(Msg.code(2447) + exception); + } + myEntityBuffered = true; + } + } + } + + @Override + public void close() { + if (myOrigHttpResponse instanceof CloseableHttpResponse) { + try { + ((CloseableHttpResponse) myOrigHttpResponse).close(); + } catch (IOException exception) { + ourLog.debug("Failed to close response", exception); + } + } + } + + @Override + public Reader createReader() throws IOException { + return new InputStreamReader(readEntity(), StandardCharsets.UTF_8); + } + + @Override + public Map> getAllHeaders() { + return myOrigHttpResponse.getAllHeaders(); + } + + @Override + public List getHeaders(String theName) { + return myOrigHttpResponse.getHeaders(theName); + } + + @Override + public String getMimeType() { + return myOrigHttpResponse.getMimeType(); + } + + @Override + public StopWatch getRequestStopWatch() { + return myOrigHttpResponse.getRequestStopWatch(); + } + + @Override + public Object getResponse() { + return null; + } + + @Override + public int getStatus() { + return myOrigHttpResponse.getStatus(); + } + + @Override + public String getStatusInfo() { + return myOrigHttpResponse.getStatusInfo(); + } + + @Override + public InputStream readEntity() { + if (myEntityBuffered) { + return new ByteArrayInputStream(myEntityBytes); + } else { + return new ByteArrayInputStream(myNewContent.getBytes()); + } + } +} diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java index 857fc4181b5..5aa920c920e 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java @@ -36,6 +36,7 @@ import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.RequestFormatParamStyleEnum; import ca.uhn.fhir.rest.api.SummaryEnum; +import ca.uhn.fhir.rest.client.api.ClientResponseContext; import ca.uhn.fhir.rest.client.api.IHttpClient; import ca.uhn.fhir.rest.client.api.IHttpRequest; import ca.uhn.fhir.rest.client.api.IHttpResponse; @@ -352,12 +353,24 @@ public abstract class BaseClient implements IRestfulClient { response = httpRequest.execute(); + final Class returnType = (binding instanceof ResourceResponseHandler) + ? ((ResourceResponseHandler) binding).getReturnType() + : null; + + final ClientResponseContext clientResponseContext = + new ClientResponseContext(httpRequest, response, this, getFhirContext(), returnType); HookParams responseParams = new HookParams(); responseParams.add(IHttpRequest.class, httpRequest); responseParams.add(IHttpResponse.class, response); responseParams.add(IRestfulClient.class, this); + responseParams.add(ClientResponseContext.class, clientResponseContext); + getInterceptorService().callHooks(Pointcut.CLIENT_RESPONSE, responseParams); + // Replace the contents of the response with whatever the hook returned, or the same response as before if + // it no-op'd + response = clientResponseContext.getHttpResponse(); + String mimeType; if (Constants.STATUS_HTTP_204_NO_CONTENT == response.getStatus()) { mimeType = null; @@ -645,6 +658,10 @@ public abstract class BaseClient implements IRestfulClient { myAllowHtmlResponse = theAllowHtmlResponse; } + public Class getReturnType() { + return myReturnType; + } + @Override public T invokeClient( String theResponseMimeType, diff --git a/hapi-fhir-converter/pom.xml b/hapi-fhir-converter/pom.xml index 199daabb89d..b92665512e4 100644 --- a/hapi-fhir-converter/pom.xml +++ b/hapi-fhir-converter/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-dist/pom.xml b/hapi-fhir-dist/pom.xml index ed159f4d302..0b3d6c514db 100644 --- a/hapi-fhir-dist/pom.xml +++ b/hapi-fhir-dist/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-docs/pom.xml b/hapi-fhir-docs/pom.xml index c8fb88de598..1031d739903 100644 --- a/hapi-fhir-docs/pom.xml +++ b/hapi-fhir-docs/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5502-base-client-client-response-pointcut-mutate-response.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5502-base-client-client-response-pointcut-mutate-response.yaml new file mode 100644 index 00000000000..31114533369 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5502-base-client-client-response-pointcut-mutate-response.yaml @@ -0,0 +1,6 @@ +--- +type: add +issue: 5502 +jira: SMILE-7262 +title: "It is now possible to mutate an HTTP response from the CLIENT_RESPONSE Pointcut, and pass this mutated response + to downstream processing." diff --git a/hapi-fhir-jacoco/pom.xml b/hapi-fhir-jacoco/pom.xml index 29520193ebd..5c60903d052 100644 --- a/hapi-fhir-jacoco/pom.xml +++ b/hapi-fhir-jacoco/pom.xml @@ -11,7 +11,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml index e217e6b86b9..3b065ee4a6e 100644 --- a/hapi-fhir-jaxrsserver-base/pom.xml +++ b/hapi-fhir-jaxrsserver-base/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpa/pom.xml b/hapi-fhir-jpa/pom.xml index 79d5c793ed2..c2c94682ffa 100644 --- a/hapi-fhir-jpa/pom.xml +++ b/hapi-fhir-jpa/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index 1ebd7add4ce..0f2f3d46345 100644 --- a/hapi-fhir-jpaserver-base/pom.xml +++ b/hapi-fhir-jpaserver-base/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml b/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml index 7e0dfd800ab..8180a9d47de 100644 --- a/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml +++ b/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-hfql/pom.xml b/hapi-fhir-jpaserver-hfql/pom.xml index bac3221b4e4..05869115705 100644 --- a/hapi-fhir-jpaserver-hfql/pom.xml +++ b/hapi-fhir-jpaserver-hfql/pom.xml @@ -3,7 +3,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-ips/pom.xml b/hapi-fhir-jpaserver-ips/pom.xml index 747bea9ba73..d470c942b64 100644 --- a/hapi-fhir-jpaserver-ips/pom.xml +++ b/hapi-fhir-jpaserver-ips/pom.xml @@ -3,7 +3,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-mdm/pom.xml b/hapi-fhir-jpaserver-mdm/pom.xml index 699b9e6e1a8..60c8a22e5ca 100644 --- a/hapi-fhir-jpaserver-mdm/pom.xml +++ b/hapi-fhir-jpaserver-mdm/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-model/pom.xml b/hapi-fhir-jpaserver-model/pom.xml index dbcafa398b4..32074d95ef6 100644 --- a/hapi-fhir-jpaserver-model/pom.xml +++ b/hapi-fhir-jpaserver-model/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-searchparam/pom.xml b/hapi-fhir-jpaserver-searchparam/pom.xml index 9e204079c6d..54a2010e026 100755 --- a/hapi-fhir-jpaserver-searchparam/pom.xml +++ b/hapi-fhir-jpaserver-searchparam/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-subscription/pom.xml b/hapi-fhir-jpaserver-subscription/pom.xml index c88d574ed07..92e6483c21a 100644 --- a/hapi-fhir-jpaserver-subscription/pom.xml +++ b/hapi-fhir-jpaserver-subscription/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-dstu2/pom.xml b/hapi-fhir-jpaserver-test-dstu2/pom.xml index b0408c3f65e..e11d32efadd 100644 --- a/hapi-fhir-jpaserver-test-dstu2/pom.xml +++ b/hapi-fhir-jpaserver-test-dstu2/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-dstu3/pom.xml b/hapi-fhir-jpaserver-test-dstu3/pom.xml index 5ac8bc1e406..9431d68d8ae 100644 --- a/hapi-fhir-jpaserver-test-dstu3/pom.xml +++ b/hapi-fhir-jpaserver-test-dstu3/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r4/pom.xml b/hapi-fhir-jpaserver-test-r4/pom.xml index 7712a6347ba..afbf5b0acd2 100644 --- a/hapi-fhir-jpaserver-test-r4/pom.xml +++ b/hapi-fhir-jpaserver-test-r4/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r4b/pom.xml b/hapi-fhir-jpaserver-test-r4b/pom.xml index 41bcc967b15..b79574ffee6 100644 --- a/hapi-fhir-jpaserver-test-r4b/pom.xml +++ b/hapi-fhir-jpaserver-test-r4b/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r5/pom.xml b/hapi-fhir-jpaserver-test-r5/pom.xml index 4e0b6f578c5..a7070be80b0 100644 --- a/hapi-fhir-jpaserver-test-r5/pom.xml +++ b/hapi-fhir-jpaserver-test-r5/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-utilities/pom.xml b/hapi-fhir-jpaserver-test-utilities/pom.xml index f9cb2801cba..ae20eb92d4c 100644 --- a/hapi-fhir-jpaserver-test-utilities/pom.xml +++ b/hapi-fhir-jpaserver-test-utilities/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml index 0bc7f2bb52a..2820eb69e50 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-server-cds-hooks/pom.xml b/hapi-fhir-server-cds-hooks/pom.xml index 13964da20d3..790ea7408be 100644 --- a/hapi-fhir-server-cds-hooks/pom.xml +++ b/hapi-fhir-server-cds-hooks/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server-mdm/pom.xml b/hapi-fhir-server-mdm/pom.xml index 43be75c5975..c940c9a1095 100644 --- a/hapi-fhir-server-mdm/pom.xml +++ b/hapi-fhir-server-mdm/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server-openapi/pom.xml b/hapi-fhir-server-openapi/pom.xml index 4b257238089..bb8306309e0 100644 --- a/hapi-fhir-server-openapi/pom.xml +++ b/hapi-fhir-server-openapi/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server/pom.xml b/hapi-fhir-server/pom.xml index 87545c43398..14226a4136d 100644 --- a/hapi-fhir-server/pom.xml +++ b/hapi-fhir-server/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml index d9bd49738d6..bb80f784fd3 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml @@ -7,7 +7,7 @@ hapi-fhir-serviceloaders ca.uhn.hapi.fhir - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml index 088209cc351..8fd0cc9c89e 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml @@ -7,7 +7,7 @@ hapi-fhir-serviceloaders ca.uhn.hapi.fhir - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../pom.xml @@ -21,7 +21,7 @@ ca.uhn.hapi.fhir hapi-fhir-caching-api - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml index 687afbf9a17..a0a001b2159 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml @@ -7,7 +7,7 @@ hapi-fhir-serviceloaders ca.uhn.hapi.fhir - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml index 87ea52486a0..0f56910a478 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml @@ -7,7 +7,7 @@ hapi-fhir ca.uhn.hapi.fhir - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../../pom.xml diff --git a/hapi-fhir-serviceloaders/pom.xml b/hapi-fhir-serviceloaders/pom.xml index 13dd141193d..3e84d9f6b65 100644 --- a/hapi-fhir-serviceloaders/pom.xml +++ b/hapi-fhir-serviceloaders/pom.xml @@ -5,7 +5,7 @@ hapi-deployable-pom ca.uhn.hapi.fhir - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml index e6c86b9552e..bb234e607ea 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml index f19a6bda58f..a576a9a2a4e 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT hapi-fhir-spring-boot-sample-client-apache diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml index f88d87a0b98..58ce092f00d 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml index 2e09e1b0da9..0804a3fe706 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml index a8ea0668e3d..719aa8d1726 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml index 6da838865ba..527947da607 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/pom.xml b/hapi-fhir-spring-boot/pom.xml index 5308ca8d290..3fa3a852d1c 100644 --- a/hapi-fhir-spring-boot/pom.xml +++ b/hapi-fhir-spring-boot/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-sql-migrate/pom.xml b/hapi-fhir-sql-migrate/pom.xml index ad912b14e25..809ed62b838 100644 --- a/hapi-fhir-sql-migrate/pom.xml +++ b/hapi-fhir-sql-migrate/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2-jobs/pom.xml b/hapi-fhir-storage-batch2-jobs/pom.xml index 665b79c1640..9ab3b2f2903 100644 --- a/hapi-fhir-storage-batch2-jobs/pom.xml +++ b/hapi-fhir-storage-batch2-jobs/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2-test-utilities/pom.xml b/hapi-fhir-storage-batch2-test-utilities/pom.xml index 5c28ea9bcd4..a470df0d3f5 100644 --- a/hapi-fhir-storage-batch2-test-utilities/pom.xml +++ b/hapi-fhir-storage-batch2-test-utilities/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2/pom.xml b/hapi-fhir-storage-batch2/pom.xml index e502c1a2cd8..7f32f86eae4 100644 --- a/hapi-fhir-storage-batch2/pom.xml +++ b/hapi-fhir-storage-batch2/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-cr/pom.xml b/hapi-fhir-storage-cr/pom.xml index 592593f7fb6..eb8714f900e 100644 --- a/hapi-fhir-storage-cr/pom.xml +++ b/hapi-fhir-storage-cr/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-mdm/pom.xml b/hapi-fhir-storage-mdm/pom.xml index e6437a1f45d..ece2c90b097 100644 --- a/hapi-fhir-storage-mdm/pom.xml +++ b/hapi-fhir-storage-mdm/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-test-utilities/pom.xml b/hapi-fhir-storage-test-utilities/pom.xml index fb29ef17446..49c62f02771 100644 --- a/hapi-fhir-storage-test-utilities/pom.xml +++ b/hapi-fhir-storage-test-utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage/pom.xml b/hapi-fhir-storage/pom.xml index e6c24290983..15157a0e06b 100644 --- a/hapi-fhir-storage/pom.xml +++ b/hapi-fhir-storage/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu2.1/pom.xml b/hapi-fhir-structures-dstu2.1/pom.xml index 3db931c5d24..cb78fbabc92 100644 --- a/hapi-fhir-structures-dstu2.1/pom.xml +++ b/hapi-fhir-structures-dstu2.1/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu2/pom.xml b/hapi-fhir-structures-dstu2/pom.xml index b606a900cd7..a082d97a136 100644 --- a/hapi-fhir-structures-dstu2/pom.xml +++ b/hapi-fhir-structures-dstu2/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu3/pom.xml b/hapi-fhir-structures-dstu3/pom.xml index 6b64efeb95c..de42c6f8a55 100644 --- a/hapi-fhir-structures-dstu3/pom.xml +++ b/hapi-fhir-structures-dstu3/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-hl7org-dstu2/pom.xml b/hapi-fhir-structures-hl7org-dstu2/pom.xml index f0e3e2214b8..fccff9966f2 100644 --- a/hapi-fhir-structures-hl7org-dstu2/pom.xml +++ b/hapi-fhir-structures-hl7org-dstu2/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r4/pom.xml b/hapi-fhir-structures-r4/pom.xml index 95bf17a3aa3..e9d282a5a0a 100644 --- a/hapi-fhir-structures-r4/pom.xml +++ b/hapi-fhir-structures-r4/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r4b/pom.xml b/hapi-fhir-structures-r4b/pom.xml index 14a7cbc6011..7db28482218 100644 --- a/hapi-fhir-structures-r4b/pom.xml +++ b/hapi-fhir-structures-r4b/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r5/pom.xml b/hapi-fhir-structures-r5/pom.xml index 80ea9d8936b..2b0d862f79a 100644 --- a/hapi-fhir-structures-r5/pom.xml +++ b/hapi-fhir-structures-r5/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-test-utilities/pom.xml b/hapi-fhir-test-utilities/pom.xml index 41e519eb390..90ea49f97e2 100644 --- a/hapi-fhir-test-utilities/pom.xml +++ b/hapi-fhir-test-utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-testpage-overlay/pom.xml b/hapi-fhir-testpage-overlay/pom.xml index e12e18330c8..1179767e72c 100644 --- a/hapi-fhir-testpage-overlay/pom.xml +++ b/hapi-fhir-testpage-overlay/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-validation-resources-dstu2.1/pom.xml b/hapi-fhir-validation-resources-dstu2.1/pom.xml index 97ea5b5fb54..8472e3bcd52 100644 --- a/hapi-fhir-validation-resources-dstu2.1/pom.xml +++ b/hapi-fhir-validation-resources-dstu2.1/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-dstu2/pom.xml b/hapi-fhir-validation-resources-dstu2/pom.xml index 83b7ddb4706..59231fef232 100644 --- a/hapi-fhir-validation-resources-dstu2/pom.xml +++ b/hapi-fhir-validation-resources-dstu2/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-dstu3/pom.xml b/hapi-fhir-validation-resources-dstu3/pom.xml index c4c98ed5d6f..da2f4a4dcf0 100644 --- a/hapi-fhir-validation-resources-dstu3/pom.xml +++ b/hapi-fhir-validation-resources-dstu3/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r4/pom.xml b/hapi-fhir-validation-resources-r4/pom.xml index 63bf9d4eae2..2755e94c2dd 100644 --- a/hapi-fhir-validation-resources-r4/pom.xml +++ b/hapi-fhir-validation-resources-r4/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r4b/pom.xml b/hapi-fhir-validation-resources-r4b/pom.xml index 3d544b03e9d..f6f6ca40000 100644 --- a/hapi-fhir-validation-resources-r4b/pom.xml +++ b/hapi-fhir-validation-resources-r4b/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r5/pom.xml b/hapi-fhir-validation-resources-r5/pom.xml index ec5a833c234..87408d2e414 100644 --- a/hapi-fhir-validation-resources-r5/pom.xml +++ b/hapi-fhir-validation-resources-r5/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation/pom.xml b/hapi-fhir-validation/pom.xml index 80b4cc6a3a1..2d34f47aca0 100644 --- a/hapi-fhir-validation/pom.xml +++ b/hapi-fhir-validation/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-tinder-plugin/pom.xml b/hapi-tinder-plugin/pom.xml index 1b06c741ab1..1f991cf66e9 100644 --- a/hapi-tinder-plugin/pom.xml +++ b/hapi-tinder-plugin/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../pom.xml diff --git a/hapi-tinder-test/pom.xml b/hapi-tinder-test/pom.xml index 339591a3322..9d249173349 100644 --- a/hapi-tinder-test/pom.xml +++ b/hapi-tinder-test/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index cc15c9fe356..a9debe628e1 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ ca.uhn.hapi.fhir hapi-fhir pom - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT HAPI-FHIR An open-source implementation of the FHIR specification in Java. diff --git a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml index 1af297a0476..91c7def0493 100644 --- a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml +++ b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../../pom.xml diff --git a/tests/hapi-fhir-base-test-mindeps-client/pom.xml b/tests/hapi-fhir-base-test-mindeps-client/pom.xml index 9996e300079..b07c2857bf4 100644 --- a/tests/hapi-fhir-base-test-mindeps-client/pom.xml +++ b/tests/hapi-fhir-base-test-mindeps-client/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../../pom.xml diff --git a/tests/hapi-fhir-base-test-mindeps-server/pom.xml b/tests/hapi-fhir-base-test-mindeps-server/pom.xml index 249b5358d82..24120ff1236 100644 --- a/tests/hapi-fhir-base-test-mindeps-server/pom.xml +++ b/tests/hapi-fhir-base-test-mindeps-server/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.2-SNAPSHOT + 6.11.3-SNAPSHOT ../../pom.xml From fab00ca3a06e39263be7d039fd9e150a1117ef5f Mon Sep 17 00:00:00 2001 From: Michael Buckley Date: Thu, 30 Nov 2023 16:43:10 -0500 Subject: [PATCH 07/14] Expose the desired Propagation in a tx template (#5516) --- hapi-deployable-pom/pom.xml | 2 +- hapi-fhir-android/pom.xml | 2 +- hapi-fhir-base/pom.xml | 2 +- hapi-fhir-bom/pom.xml | 4 +- hapi-fhir-checkstyle/pom.xml | 2 +- hapi-fhir-cli/hapi-fhir-cli-api/pom.xml | 2 +- hapi-fhir-cli/hapi-fhir-cli-app/pom.xml | 2 +- hapi-fhir-cli/pom.xml | 2 +- hapi-fhir-client-okhttp/pom.xml | 2 +- hapi-fhir-client/pom.xml | 2 +- hapi-fhir-converter/pom.xml | 2 +- hapi-fhir-dist/pom.xml | 2 +- hapi-fhir-docs/pom.xml | 2 +- hapi-fhir-jacoco/pom.xml | 2 +- hapi-fhir-jaxrsserver-base/pom.xml | 2 +- hapi-fhir-jpa/pom.xml | 2 +- hapi-fhir-jpaserver-base/pom.xml | 2 +- .../pom.xml | 2 +- hapi-fhir-jpaserver-hfql/pom.xml | 2 +- hapi-fhir-jpaserver-ips/pom.xml | 2 +- hapi-fhir-jpaserver-mdm/pom.xml | 2 +- hapi-fhir-jpaserver-model/pom.xml | 2 +- hapi-fhir-jpaserver-searchparam/pom.xml | 2 +- hapi-fhir-jpaserver-subscription/pom.xml | 2 +- hapi-fhir-jpaserver-test-dstu2/pom.xml | 2 +- hapi-fhir-jpaserver-test-dstu3/pom.xml | 2 +- hapi-fhir-jpaserver-test-r4/pom.xml | 2 +- hapi-fhir-jpaserver-test-r4b/pom.xml | 2 +- hapi-fhir-jpaserver-test-r5/pom.xml | 2 +- hapi-fhir-jpaserver-test-utilities/pom.xml | 2 +- .../util/JpaHapiTransactionServiceTest.java | 115 ++++++++++++++++++ hapi-fhir-jpaserver-uhnfhirtest/pom.xml | 2 +- hapi-fhir-server-cds-hooks/pom.xml | 2 +- hapi-fhir-server-mdm/pom.xml | 2 +- hapi-fhir-server-openapi/pom.xml | 2 +- hapi-fhir-server/pom.xml | 2 +- .../hapi-fhir-caching-api/pom.xml | 2 +- .../hapi-fhir-caching-caffeine/pom.xml | 4 +- .../hapi-fhir-caching-guava/pom.xml | 2 +- .../hapi-fhir-caching-testing/pom.xml | 2 +- hapi-fhir-serviceloaders/pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../hapi-fhir-spring-boot-samples/pom.xml | 2 +- .../hapi-fhir-spring-boot-starter/pom.xml | 2 +- hapi-fhir-spring-boot/pom.xml | 2 +- hapi-fhir-sql-migrate/pom.xml | 2 +- hapi-fhir-storage-batch2-jobs/pom.xml | 2 +- .../pom.xml | 2 +- hapi-fhir-storage-batch2/pom.xml | 2 +- hapi-fhir-storage-cr/pom.xml | 2 +- hapi-fhir-storage-mdm/pom.xml | 2 +- hapi-fhir-storage-test-utilities/pom.xml | 2 +- hapi-fhir-storage/pom.xml | 2 +- .../jpa/dao/tx/HapiTransactionService.java | 4 + hapi-fhir-structures-dstu2.1/pom.xml | 2 +- hapi-fhir-structures-dstu2/pom.xml | 2 +- hapi-fhir-structures-dstu3/pom.xml | 2 +- hapi-fhir-structures-hl7org-dstu2/pom.xml | 2 +- hapi-fhir-structures-r4/pom.xml | 2 +- hapi-fhir-structures-r4b/pom.xml | 2 +- hapi-fhir-structures-r5/pom.xml | 2 +- hapi-fhir-test-utilities/pom.xml | 2 +- hapi-fhir-testpage-overlay/pom.xml | 2 +- .../pom.xml | 2 +- hapi-fhir-validation-resources-dstu2/pom.xml | 2 +- hapi-fhir-validation-resources-dstu3/pom.xml | 2 +- hapi-fhir-validation-resources-r4/pom.xml | 2 +- hapi-fhir-validation-resources-r4b/pom.xml | 2 +- hapi-fhir-validation-resources-r5/pom.xml | 2 +- hapi-fhir-validation/pom.xml | 2 +- hapi-tinder-plugin/pom.xml | 2 +- hapi-tinder-test/pom.xml | 2 +- pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- 79 files changed, 198 insertions(+), 79 deletions(-) create mode 100644 hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/util/JpaHapiTransactionServiceTest.java diff --git a/hapi-deployable-pom/pom.xml b/hapi-deployable-pom/pom.xml index 25f29f27be9..7828ab2b979 100644 --- a/hapi-deployable-pom/pom.xml +++ b/hapi-deployable-pom/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-android/pom.xml b/hapi-fhir-android/pom.xml index 6ffe01c5578..c80236908a1 100644 --- a/hapi-fhir-android/pom.xml +++ b/hapi-fhir-android/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml index e67b3027b07..ddb95cf5e41 100644 --- a/hapi-fhir-base/pom.xml +++ b/hapi-fhir-base/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-bom/pom.xml b/hapi-fhir-bom/pom.xml index 8b175d80acc..80805431db2 100644 --- a/hapi-fhir-bom/pom.xml +++ b/hapi-fhir-bom/pom.xml @@ -4,7 +4,7 @@ 4.0.0 ca.uhn.hapi.fhir hapi-fhir-bom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT pom HAPI FHIR BOM @@ -12,7 +12,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-checkstyle/pom.xml b/hapi-fhir-checkstyle/pom.xml index 9819c07f04a..75efb8b7607 100644 --- a/hapi-fhir-checkstyle/pom.xml +++ b/hapi-fhir-checkstyle/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml index cfcaebca7a9..953d36a6f18 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml index 0bbc80ea717..dd37903542f 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir-cli - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-cli/pom.xml b/hapi-fhir-cli/pom.xml index 86345f32a31..b2fe5bc90b3 100644 --- a/hapi-fhir-cli/pom.xml +++ b/hapi-fhir-cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-client-okhttp/pom.xml b/hapi-fhir-client-okhttp/pom.xml index 0d687f7b1b0..70061eae755 100644 --- a/hapi-fhir-client-okhttp/pom.xml +++ b/hapi-fhir-client-okhttp/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-client/pom.xml b/hapi-fhir-client/pom.xml index 0f0e49a5f1b..1f8a6b2d2f6 100644 --- a/hapi-fhir-client/pom.xml +++ b/hapi-fhir-client/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-converter/pom.xml b/hapi-fhir-converter/pom.xml index b92665512e4..bcaa65bb8cb 100644 --- a/hapi-fhir-converter/pom.xml +++ b/hapi-fhir-converter/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-dist/pom.xml b/hapi-fhir-dist/pom.xml index 0b3d6c514db..bd14759edc9 100644 --- a/hapi-fhir-dist/pom.xml +++ b/hapi-fhir-dist/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-docs/pom.xml b/hapi-fhir-docs/pom.xml index 1031d739903..ce9d6c844ce 100644 --- a/hapi-fhir-docs/pom.xml +++ b/hapi-fhir-docs/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jacoco/pom.xml b/hapi-fhir-jacoco/pom.xml index 5c60903d052..47c0a8dc304 100644 --- a/hapi-fhir-jacoco/pom.xml +++ b/hapi-fhir-jacoco/pom.xml @@ -11,7 +11,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml index 3b065ee4a6e..e4b20f53161 100644 --- a/hapi-fhir-jaxrsserver-base/pom.xml +++ b/hapi-fhir-jaxrsserver-base/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpa/pom.xml b/hapi-fhir-jpa/pom.xml index c2c94682ffa..076d8328a69 100644 --- a/hapi-fhir-jpa/pom.xml +++ b/hapi-fhir-jpa/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index 0f2f3d46345..da09b2a7d82 100644 --- a/hapi-fhir-jpaserver-base/pom.xml +++ b/hapi-fhir-jpaserver-base/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml b/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml index 8180a9d47de..6fcd53ab9ab 100644 --- a/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml +++ b/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-hfql/pom.xml b/hapi-fhir-jpaserver-hfql/pom.xml index 05869115705..1d71e2ac1e7 100644 --- a/hapi-fhir-jpaserver-hfql/pom.xml +++ b/hapi-fhir-jpaserver-hfql/pom.xml @@ -3,7 +3,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-ips/pom.xml b/hapi-fhir-jpaserver-ips/pom.xml index d470c942b64..5c28e18021a 100644 --- a/hapi-fhir-jpaserver-ips/pom.xml +++ b/hapi-fhir-jpaserver-ips/pom.xml @@ -3,7 +3,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-mdm/pom.xml b/hapi-fhir-jpaserver-mdm/pom.xml index 60c8a22e5ca..2fb8fa175ad 100644 --- a/hapi-fhir-jpaserver-mdm/pom.xml +++ b/hapi-fhir-jpaserver-mdm/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-model/pom.xml b/hapi-fhir-jpaserver-model/pom.xml index 32074d95ef6..e161b491e6b 100644 --- a/hapi-fhir-jpaserver-model/pom.xml +++ b/hapi-fhir-jpaserver-model/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-searchparam/pom.xml b/hapi-fhir-jpaserver-searchparam/pom.xml index 54a2010e026..051a3069018 100755 --- a/hapi-fhir-jpaserver-searchparam/pom.xml +++ b/hapi-fhir-jpaserver-searchparam/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-subscription/pom.xml b/hapi-fhir-jpaserver-subscription/pom.xml index 92e6483c21a..9b119ecea50 100644 --- a/hapi-fhir-jpaserver-subscription/pom.xml +++ b/hapi-fhir-jpaserver-subscription/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-dstu2/pom.xml b/hapi-fhir-jpaserver-test-dstu2/pom.xml index e11d32efadd..177675ad5a5 100644 --- a/hapi-fhir-jpaserver-test-dstu2/pom.xml +++ b/hapi-fhir-jpaserver-test-dstu2/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-dstu3/pom.xml b/hapi-fhir-jpaserver-test-dstu3/pom.xml index 9431d68d8ae..83dde3e9706 100644 --- a/hapi-fhir-jpaserver-test-dstu3/pom.xml +++ b/hapi-fhir-jpaserver-test-dstu3/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r4/pom.xml b/hapi-fhir-jpaserver-test-r4/pom.xml index afbf5b0acd2..79a1bddbeaa 100644 --- a/hapi-fhir-jpaserver-test-r4/pom.xml +++ b/hapi-fhir-jpaserver-test-r4/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r4b/pom.xml b/hapi-fhir-jpaserver-test-r4b/pom.xml index b79574ffee6..df49d1e866a 100644 --- a/hapi-fhir-jpaserver-test-r4b/pom.xml +++ b/hapi-fhir-jpaserver-test-r4b/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r5/pom.xml b/hapi-fhir-jpaserver-test-r5/pom.xml index a7070be80b0..3e7ea400eca 100644 --- a/hapi-fhir-jpaserver-test-r5/pom.xml +++ b/hapi-fhir-jpaserver-test-r5/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-utilities/pom.xml b/hapi-fhir-jpaserver-test-utilities/pom.xml index ae20eb92d4c..a1bf33ca001 100644 --- a/hapi-fhir-jpaserver-test-utilities/pom.xml +++ b/hapi-fhir-jpaserver-test-utilities/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/util/JpaHapiTransactionServiceTest.java b/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/util/JpaHapiTransactionServiceTest.java new file mode 100644 index 00000000000..9f3d588d923 --- /dev/null +++ b/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/util/JpaHapiTransactionServiceTest.java @@ -0,0 +1,115 @@ +package ca.uhn.fhir.jpa.util; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService; +import ca.uhn.fhir.jpa.test.BaseJpaTest; +import ca.uhn.fhir.jpa.test.config.TestR4Config; +import ca.uhn.fhir.rest.api.server.SystemRequestDetails; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Patient; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.Propagation; + +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = { + TestR4Config.class +}) +public class JpaHapiTransactionServiceTest extends BaseJpaTest { + @Autowired PlatformTransactionManager myTxManager; + @Autowired IFhirResourceDao myPatientDao; + @Autowired IFhirResourceDao myObservationDao; + SystemRequestDetails myRequestDetails = new SystemRequestDetails(); + final AtomicReference myObservationId = new AtomicReference<>(); + final AtomicReference myPatientId = new AtomicReference<>(); + + @Override + protected FhirContext getFhirContext() { + return myFhirContext; + } + + @Override + protected PlatformTransactionManager getTxManager() { + return myTxManager; + } + + @Autowired + HapiTransactionService myHapiTransactionService; + + @Test + void testNewTransactionCommitInsideOldTransactionRollback() { + + try { + myHapiTransactionService.withSystemRequest().withPropagation(Propagation.REQUIRED).execute(()->{ + myObservationId.set(myObservationDao.create(new Observation(), myRequestDetails).getId()); + + myHapiTransactionService.withSystemRequest().withPropagation(Propagation.REQUIRES_NEW) + .execute(()-> myPatientId.set(myPatientDao.create(new Patient(), myRequestDetails).getId())); + // roll back the Observation. The Patient has committed + throw new RuntimeException("roll back the Observation."); + }); + } catch (RuntimeException e) { + // expected + } + + assertNotFound(myObservationDao, myObservationId.get()); + assertFound(myPatientDao, myPatientId.get()); + } + + + + @Test + void testRequiredTransactionCommitInsideExistingTx_rollsBackWithMainTx() { + // given + + try { + myHapiTransactionService.withSystemRequest().withPropagation(Propagation.REQUIRED).execute(()->{ + myObservationId.set(myObservationDao.create(new Observation(), myRequestDetails).getId()); + + myHapiTransactionService.withSystemRequest().withPropagation(Propagation.REQUIRED).execute(()-> myPatientId.set(myPatientDao.create(new Patient(), myRequestDetails).getId())); + throw new RuntimeException("roll back both."); + }); + } catch (RuntimeException e) { + // expected + } + + assertNotFound(myObservationDao, myObservationId.get()); + assertNotFound(myPatientDao, myPatientId.get()); + } + + @Test + void testTransactionCommitRespectsRollbackOnly() { + + try { + myHapiTransactionService.withSystemRequest().withPropagation(Propagation.REQUIRED).execute((theTransactionStatus)->{ + myObservationId.set(myObservationDao.create(new Observation(), myRequestDetails).getId()); + theTransactionStatus.setRollbackOnly(); + return null; + }); + } catch (RuntimeException e) { + // expected + } + + assertNotFound(myObservationDao, myObservationId.get()); + } + + void assertNotFound(IFhirResourceDao theDao, IIdType id) { + assertThrows(ResourceNotFoundException.class, ()-> theDao.read(id, myRequestDetails)); + } + + void assertFound(IFhirResourceDao theDao, IIdType theId) { + assertNotNull(theDao.read(theId, myRequestDetails)); + } +} diff --git a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml index 2820eb69e50..539f240c9a6 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-server-cds-hooks/pom.xml b/hapi-fhir-server-cds-hooks/pom.xml index 790ea7408be..4ab4abd28ef 100644 --- a/hapi-fhir-server-cds-hooks/pom.xml +++ b/hapi-fhir-server-cds-hooks/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server-mdm/pom.xml b/hapi-fhir-server-mdm/pom.xml index c940c9a1095..35f51c77d5b 100644 --- a/hapi-fhir-server-mdm/pom.xml +++ b/hapi-fhir-server-mdm/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server-openapi/pom.xml b/hapi-fhir-server-openapi/pom.xml index bb8306309e0..5d58f58def4 100644 --- a/hapi-fhir-server-openapi/pom.xml +++ b/hapi-fhir-server-openapi/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server/pom.xml b/hapi-fhir-server/pom.xml index 14226a4136d..aaa176420d4 100644 --- a/hapi-fhir-server/pom.xml +++ b/hapi-fhir-server/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml index bb80f784fd3..2efd21502bb 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml @@ -7,7 +7,7 @@ hapi-fhir-serviceloaders ca.uhn.hapi.fhir - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml index 8fd0cc9c89e..3db18cec3bb 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml @@ -7,7 +7,7 @@ hapi-fhir-serviceloaders ca.uhn.hapi.fhir - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../pom.xml @@ -21,7 +21,7 @@ ca.uhn.hapi.fhir hapi-fhir-caching-api - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml index a0a001b2159..341ac8b5cfd 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml @@ -7,7 +7,7 @@ hapi-fhir-serviceloaders ca.uhn.hapi.fhir - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml index 0f56910a478..ef1e667f1e8 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml @@ -7,7 +7,7 @@ hapi-fhir ca.uhn.hapi.fhir - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../../pom.xml diff --git a/hapi-fhir-serviceloaders/pom.xml b/hapi-fhir-serviceloaders/pom.xml index 3e84d9f6b65..b2ffa705596 100644 --- a/hapi-fhir-serviceloaders/pom.xml +++ b/hapi-fhir-serviceloaders/pom.xml @@ -5,7 +5,7 @@ hapi-deployable-pom ca.uhn.hapi.fhir - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml index bb234e607ea..627cefbce72 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml index a576a9a2a4e..1c49210557f 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT hapi-fhir-spring-boot-sample-client-apache diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml index 58ce092f00d..e7e3ddb3c43 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml index 0804a3fe706..90d0122dc8c 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml index 719aa8d1726..ee7267fda57 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml index 527947da607..9c442072b80 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/pom.xml b/hapi-fhir-spring-boot/pom.xml index 3fa3a852d1c..4e9363295a7 100644 --- a/hapi-fhir-spring-boot/pom.xml +++ b/hapi-fhir-spring-boot/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-sql-migrate/pom.xml b/hapi-fhir-sql-migrate/pom.xml index 809ed62b838..53ddb291362 100644 --- a/hapi-fhir-sql-migrate/pom.xml +++ b/hapi-fhir-sql-migrate/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2-jobs/pom.xml b/hapi-fhir-storage-batch2-jobs/pom.xml index 9ab3b2f2903..87c80e9aea2 100644 --- a/hapi-fhir-storage-batch2-jobs/pom.xml +++ b/hapi-fhir-storage-batch2-jobs/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2-test-utilities/pom.xml b/hapi-fhir-storage-batch2-test-utilities/pom.xml index a470df0d3f5..ab92cdf2bff 100644 --- a/hapi-fhir-storage-batch2-test-utilities/pom.xml +++ b/hapi-fhir-storage-batch2-test-utilities/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2/pom.xml b/hapi-fhir-storage-batch2/pom.xml index 7f32f86eae4..5329617d4fc 100644 --- a/hapi-fhir-storage-batch2/pom.xml +++ b/hapi-fhir-storage-batch2/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-cr/pom.xml b/hapi-fhir-storage-cr/pom.xml index eb8714f900e..aaa18119b78 100644 --- a/hapi-fhir-storage-cr/pom.xml +++ b/hapi-fhir-storage-cr/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-mdm/pom.xml b/hapi-fhir-storage-mdm/pom.xml index ece2c90b097..2af9a4e6dbc 100644 --- a/hapi-fhir-storage-mdm/pom.xml +++ b/hapi-fhir-storage-mdm/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-test-utilities/pom.xml b/hapi-fhir-storage-test-utilities/pom.xml index 49c62f02771..9a46e263b1e 100644 --- a/hapi-fhir-storage-test-utilities/pom.xml +++ b/hapi-fhir-storage-test-utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage/pom.xml b/hapi-fhir-storage/pom.xml index 15157a0e06b..d5a2887d24f 100644 --- a/hapi-fhir-storage/pom.xml +++ b/hapi-fhir-storage/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/tx/HapiTransactionService.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/tx/HapiTransactionService.java index 58a7ee5c679..348dce762be 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/tx/HapiTransactionService.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/tx/HapiTransactionService.java @@ -489,6 +489,10 @@ public class HapiTransactionService implements IHapiTransactionService { public RequestDetails getRequestDetailsForTesting() { return myRequestDetails; } + + public Propagation getPropagation() { + return myPropagation; + } } /** diff --git a/hapi-fhir-structures-dstu2.1/pom.xml b/hapi-fhir-structures-dstu2.1/pom.xml index cb78fbabc92..58a42fc146f 100644 --- a/hapi-fhir-structures-dstu2.1/pom.xml +++ b/hapi-fhir-structures-dstu2.1/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu2/pom.xml b/hapi-fhir-structures-dstu2/pom.xml index a082d97a136..94b617d3ad7 100644 --- a/hapi-fhir-structures-dstu2/pom.xml +++ b/hapi-fhir-structures-dstu2/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu3/pom.xml b/hapi-fhir-structures-dstu3/pom.xml index de42c6f8a55..380194ed555 100644 --- a/hapi-fhir-structures-dstu3/pom.xml +++ b/hapi-fhir-structures-dstu3/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-hl7org-dstu2/pom.xml b/hapi-fhir-structures-hl7org-dstu2/pom.xml index fccff9966f2..4f4e5b841be 100644 --- a/hapi-fhir-structures-hl7org-dstu2/pom.xml +++ b/hapi-fhir-structures-hl7org-dstu2/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r4/pom.xml b/hapi-fhir-structures-r4/pom.xml index e9d282a5a0a..b537170eff6 100644 --- a/hapi-fhir-structures-r4/pom.xml +++ b/hapi-fhir-structures-r4/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r4b/pom.xml b/hapi-fhir-structures-r4b/pom.xml index 7db28482218..41b8084fbc6 100644 --- a/hapi-fhir-structures-r4b/pom.xml +++ b/hapi-fhir-structures-r4b/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r5/pom.xml b/hapi-fhir-structures-r5/pom.xml index 2b0d862f79a..45390f8b466 100644 --- a/hapi-fhir-structures-r5/pom.xml +++ b/hapi-fhir-structures-r5/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-test-utilities/pom.xml b/hapi-fhir-test-utilities/pom.xml index 90ea49f97e2..6da65fbc247 100644 --- a/hapi-fhir-test-utilities/pom.xml +++ b/hapi-fhir-test-utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-testpage-overlay/pom.xml b/hapi-fhir-testpage-overlay/pom.xml index 1179767e72c..e4e80515b04 100644 --- a/hapi-fhir-testpage-overlay/pom.xml +++ b/hapi-fhir-testpage-overlay/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-validation-resources-dstu2.1/pom.xml b/hapi-fhir-validation-resources-dstu2.1/pom.xml index 8472e3bcd52..efe3441f8b6 100644 --- a/hapi-fhir-validation-resources-dstu2.1/pom.xml +++ b/hapi-fhir-validation-resources-dstu2.1/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-dstu2/pom.xml b/hapi-fhir-validation-resources-dstu2/pom.xml index 59231fef232..d93073774c7 100644 --- a/hapi-fhir-validation-resources-dstu2/pom.xml +++ b/hapi-fhir-validation-resources-dstu2/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-dstu3/pom.xml b/hapi-fhir-validation-resources-dstu3/pom.xml index da2f4a4dcf0..1edb8b6baff 100644 --- a/hapi-fhir-validation-resources-dstu3/pom.xml +++ b/hapi-fhir-validation-resources-dstu3/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r4/pom.xml b/hapi-fhir-validation-resources-r4/pom.xml index 2755e94c2dd..3bfc892fb39 100644 --- a/hapi-fhir-validation-resources-r4/pom.xml +++ b/hapi-fhir-validation-resources-r4/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r4b/pom.xml b/hapi-fhir-validation-resources-r4b/pom.xml index f6f6ca40000..cb8e68e7ae2 100644 --- a/hapi-fhir-validation-resources-r4b/pom.xml +++ b/hapi-fhir-validation-resources-r4b/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r5/pom.xml b/hapi-fhir-validation-resources-r5/pom.xml index 87408d2e414..9598eebf464 100644 --- a/hapi-fhir-validation-resources-r5/pom.xml +++ b/hapi-fhir-validation-resources-r5/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation/pom.xml b/hapi-fhir-validation/pom.xml index 2d34f47aca0..6713e8e8969 100644 --- a/hapi-fhir-validation/pom.xml +++ b/hapi-fhir-validation/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-tinder-plugin/pom.xml b/hapi-tinder-plugin/pom.xml index 1f991cf66e9..bcad4078b61 100644 --- a/hapi-tinder-plugin/pom.xml +++ b/hapi-tinder-plugin/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../pom.xml diff --git a/hapi-tinder-test/pom.xml b/hapi-tinder-test/pom.xml index 9d249173349..abe72e3b4d9 100644 --- a/hapi-tinder-test/pom.xml +++ b/hapi-tinder-test/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index a9debe628e1..c0d8d46983e 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ ca.uhn.hapi.fhir hapi-fhir pom - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT HAPI-FHIR An open-source implementation of the FHIR specification in Java. diff --git a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml index 91c7def0493..e3d6f1f5c15 100644 --- a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml +++ b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../../pom.xml diff --git a/tests/hapi-fhir-base-test-mindeps-client/pom.xml b/tests/hapi-fhir-base-test-mindeps-client/pom.xml index b07c2857bf4..88a64d42780 100644 --- a/tests/hapi-fhir-base-test-mindeps-client/pom.xml +++ b/tests/hapi-fhir-base-test-mindeps-client/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../../pom.xml diff --git a/tests/hapi-fhir-base-test-mindeps-server/pom.xml b/tests/hapi-fhir-base-test-mindeps-server/pom.xml index 24120ff1236..4ad712cf23d 100644 --- a/tests/hapi-fhir-base-test-mindeps-server/pom.xml +++ b/tests/hapi-fhir-base-test-mindeps-server/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.3-SNAPSHOT + 6.11.4-SNAPSHOT ../../pom.xml From ee4ecacbdb9a96e63ddd3f75afeaea44cf781582 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 30 Nov 2023 18:10:05 -0500 Subject: [PATCH 08/14] Revert changes for 5502. --- .../java/ca/uhn/fhir/interceptor/api/Pointcut.java | 7 +------ .../java/ca/uhn/fhir/rest/client/impl/BaseClient.java | 11 ----------- ...ient-client-response-pointcut-mutate-response.yaml | 6 ------ 3 files changed, 1 insertion(+), 23 deletions(-) delete mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5502-base-client-client-response-pointcut-mutate-response.yaml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java index 64b7267f63f..994ea9cd1cf 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java @@ -93,10 +93,6 @@ public enum Pointcut implements IPointcut { *

  • * ca.uhn.fhir.rest.client.api.IRestfulClient - The client object making the request *
  • - *
  • - * ca.uhn.fhir.rest.client.api.ClientResponseContext - Contains an IHttpRequest, an IHttpResponse, and an IRestfulClient - * and also allows the client to mutate the contained IHttpResponse - *
  • * *

    * Hook methods must return void. @@ -105,8 +101,7 @@ public enum Pointcut implements IPointcut { void.class, "ca.uhn.fhir.rest.client.api.IHttpRequest", "ca.uhn.fhir.rest.client.api.IHttpResponse", - "ca.uhn.fhir.rest.client.api.IRestfulClient", - "ca.uhn.fhir.rest.client.api.ClientResponseContext"), + "ca.uhn.fhir.rest.client.api.IRestfulClient"), /** * Server Hook: diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java index 5aa920c920e..4d05511d724 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java @@ -353,24 +353,13 @@ public abstract class BaseClient implements IRestfulClient { response = httpRequest.execute(); - final Class returnType = (binding instanceof ResourceResponseHandler) - ? ((ResourceResponseHandler) binding).getReturnType() - : null; - - final ClientResponseContext clientResponseContext = - new ClientResponseContext(httpRequest, response, this, getFhirContext(), returnType); HookParams responseParams = new HookParams(); responseParams.add(IHttpRequest.class, httpRequest); responseParams.add(IHttpResponse.class, response); responseParams.add(IRestfulClient.class, this); - responseParams.add(ClientResponseContext.class, clientResponseContext); getInterceptorService().callHooks(Pointcut.CLIENT_RESPONSE, responseParams); - // Replace the contents of the response with whatever the hook returned, or the same response as before if - // it no-op'd - response = clientResponseContext.getHttpResponse(); - String mimeType; if (Constants.STATUS_HTTP_204_NO_CONTENT == response.getStatus()) { mimeType = null; diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5502-base-client-client-response-pointcut-mutate-response.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5502-base-client-client-response-pointcut-mutate-response.yaml deleted file mode 100644 index 31114533369..00000000000 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5502-base-client-client-response-pointcut-mutate-response.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -type: add -issue: 5502 -jira: SMILE-7262 -title: "It is now possible to mutate an HTTP response from the CLIENT_RESPONSE Pointcut, and pass this mutated response - to downstream processing." From 8237c0795d74b43d6676b92041188cec04a5fcda Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 30 Nov 2023 18:23:13 -0500 Subject: [PATCH 09/14] test change --- .../src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java | 1 - 1 file changed, 1 deletion(-) diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java index 4d05511d724..f9bcbd48a00 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java @@ -36,7 +36,6 @@ import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.RequestFormatParamStyleEnum; import ca.uhn.fhir.rest.api.SummaryEnum; -import ca.uhn.fhir.rest.client.api.ClientResponseContext; import ca.uhn.fhir.rest.client.api.IHttpClient; import ca.uhn.fhir.rest.client.api.IHttpRequest; import ca.uhn.fhir.rest.client.api.IHttpResponse; From 0c8258f93ce55dc4febc6f4cdc6dcd9f1b0b179b Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 30 Nov 2023 18:54:49 -0500 Subject: [PATCH 10/14] Revert the revert --- .../java/ca/uhn/fhir/interceptor/api/Pointcut.java | 7 ++++++- .../ca/uhn/fhir/rest/client/impl/BaseClient.java | 12 ++++++++++++ ...ent-client-response-pointcut-mutate-response.yaml | 6 ++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5502-base-client-client-response-pointcut-mutate-response.yaml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java index 994ea9cd1cf..64b7267f63f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java @@ -93,6 +93,10 @@ public enum Pointcut implements IPointcut { *
  • * ca.uhn.fhir.rest.client.api.IRestfulClient - The client object making the request *
  • + *
  • + * ca.uhn.fhir.rest.client.api.ClientResponseContext - Contains an IHttpRequest, an IHttpResponse, and an IRestfulClient + * and also allows the client to mutate the contained IHttpResponse + *
  • * *

    * Hook methods must return void. @@ -101,7 +105,8 @@ public enum Pointcut implements IPointcut { void.class, "ca.uhn.fhir.rest.client.api.IHttpRequest", "ca.uhn.fhir.rest.client.api.IHttpResponse", - "ca.uhn.fhir.rest.client.api.IRestfulClient"), + "ca.uhn.fhir.rest.client.api.IRestfulClient", + "ca.uhn.fhir.rest.client.api.ClientResponseContext"), /** * Server Hook: diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java index f9bcbd48a00..5aa920c920e 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java @@ -36,6 +36,7 @@ import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.RequestFormatParamStyleEnum; import ca.uhn.fhir.rest.api.SummaryEnum; +import ca.uhn.fhir.rest.client.api.ClientResponseContext; import ca.uhn.fhir.rest.client.api.IHttpClient; import ca.uhn.fhir.rest.client.api.IHttpRequest; import ca.uhn.fhir.rest.client.api.IHttpResponse; @@ -352,13 +353,24 @@ public abstract class BaseClient implements IRestfulClient { response = httpRequest.execute(); + final Class returnType = (binding instanceof ResourceResponseHandler) + ? ((ResourceResponseHandler) binding).getReturnType() + : null; + + final ClientResponseContext clientResponseContext = + new ClientResponseContext(httpRequest, response, this, getFhirContext(), returnType); HookParams responseParams = new HookParams(); responseParams.add(IHttpRequest.class, httpRequest); responseParams.add(IHttpResponse.class, response); responseParams.add(IRestfulClient.class, this); + responseParams.add(ClientResponseContext.class, clientResponseContext); getInterceptorService().callHooks(Pointcut.CLIENT_RESPONSE, responseParams); + // Replace the contents of the response with whatever the hook returned, or the same response as before if + // it no-op'd + response = clientResponseContext.getHttpResponse(); + String mimeType; if (Constants.STATUS_HTTP_204_NO_CONTENT == response.getStatus()) { mimeType = null; diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5502-base-client-client-response-pointcut-mutate-response.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5502-base-client-client-response-pointcut-mutate-response.yaml new file mode 100644 index 00000000000..31114533369 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5502-base-client-client-response-pointcut-mutate-response.yaml @@ -0,0 +1,6 @@ +--- +type: add +issue: 5502 +jira: SMILE-7262 +title: "It is now possible to mutate an HTTP response from the CLIENT_RESPONSE Pointcut, and pass this mutated response + to downstream processing." From 59936ee70ca13dc5c393e2d42c3cbb6a9bb52005 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 1 Dec 2023 04:02:54 -0500 Subject: [PATCH 11/14] Revert 5502. (#5521) --- .../main/java/ca/uhn/fhir/interceptor/api/Pointcut.java | 3 +-- .../java/ca/uhn/fhir/rest/client/impl/BaseClient.java | 8 -------- ...e-client-client-response-pointcut-mutate-response.yaml | 6 ------ 3 files changed, 1 insertion(+), 16 deletions(-) delete mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5502-base-client-client-response-pointcut-mutate-response.yaml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java index 64b7267f63f..388f65b4c33 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java @@ -105,8 +105,7 @@ public enum Pointcut implements IPointcut { void.class, "ca.uhn.fhir.rest.client.api.IHttpRequest", "ca.uhn.fhir.rest.client.api.IHttpResponse", - "ca.uhn.fhir.rest.client.api.IRestfulClient", - "ca.uhn.fhir.rest.client.api.ClientResponseContext"), + "ca.uhn.fhir.rest.client.api.IRestfulClient"), /** * Server Hook: diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java index 5aa920c920e..8acd05255c2 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java @@ -36,7 +36,6 @@ import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.RequestFormatParamStyleEnum; import ca.uhn.fhir.rest.api.SummaryEnum; -import ca.uhn.fhir.rest.client.api.ClientResponseContext; import ca.uhn.fhir.rest.client.api.IHttpClient; import ca.uhn.fhir.rest.client.api.IHttpRequest; import ca.uhn.fhir.rest.client.api.IHttpResponse; @@ -357,20 +356,13 @@ public abstract class BaseClient implements IRestfulClient { ? ((ResourceResponseHandler) binding).getReturnType() : null; - final ClientResponseContext clientResponseContext = - new ClientResponseContext(httpRequest, response, this, getFhirContext(), returnType); HookParams responseParams = new HookParams(); responseParams.add(IHttpRequest.class, httpRequest); responseParams.add(IHttpResponse.class, response); responseParams.add(IRestfulClient.class, this); - responseParams.add(ClientResponseContext.class, clientResponseContext); getInterceptorService().callHooks(Pointcut.CLIENT_RESPONSE, responseParams); - // Replace the contents of the response with whatever the hook returned, or the same response as before if - // it no-op'd - response = clientResponseContext.getHttpResponse(); - String mimeType; if (Constants.STATUS_HTTP_204_NO_CONTENT == response.getStatus()) { mimeType = null; diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5502-base-client-client-response-pointcut-mutate-response.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5502-base-client-client-response-pointcut-mutate-response.yaml deleted file mode 100644 index 31114533369..00000000000 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5502-base-client-client-response-pointcut-mutate-response.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -type: add -issue: 5502 -jira: SMILE-7262 -title: "It is now possible to mutate an HTTP response from the CLIENT_RESPONSE Pointcut, and pass this mutated response - to downstream processing." From ec5402abd4222b2cc486fdf55d70b712e2481c52 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 1 Dec 2023 10:16:56 -0500 Subject: [PATCH 12/14] Restore changes for 5502. (#5525) * Restore changes for 5502. * Spotless. --- .../main/java/ca/uhn/fhir/interceptor/api/Pointcut.java | 3 ++- .../java/ca/uhn/fhir/rest/client/impl/BaseClient.java | 8 ++++++++ ...e-client-client-response-pointcut-mutate-response.yaml | 6 ++++++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5502-base-client-client-response-pointcut-mutate-response.yaml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java index 388f65b4c33..64b7267f63f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java @@ -105,7 +105,8 @@ public enum Pointcut implements IPointcut { void.class, "ca.uhn.fhir.rest.client.api.IHttpRequest", "ca.uhn.fhir.rest.client.api.IHttpResponse", - "ca.uhn.fhir.rest.client.api.IRestfulClient"), + "ca.uhn.fhir.rest.client.api.IRestfulClient", + "ca.uhn.fhir.rest.client.api.ClientResponseContext"), /** * Server Hook: diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java index 8acd05255c2..5aa920c920e 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/BaseClient.java @@ -36,6 +36,7 @@ import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.RequestFormatParamStyleEnum; import ca.uhn.fhir.rest.api.SummaryEnum; +import ca.uhn.fhir.rest.client.api.ClientResponseContext; import ca.uhn.fhir.rest.client.api.IHttpClient; import ca.uhn.fhir.rest.client.api.IHttpRequest; import ca.uhn.fhir.rest.client.api.IHttpResponse; @@ -356,13 +357,20 @@ public abstract class BaseClient implements IRestfulClient { ? ((ResourceResponseHandler) binding).getReturnType() : null; + final ClientResponseContext clientResponseContext = + new ClientResponseContext(httpRequest, response, this, getFhirContext(), returnType); HookParams responseParams = new HookParams(); responseParams.add(IHttpRequest.class, httpRequest); responseParams.add(IHttpResponse.class, response); responseParams.add(IRestfulClient.class, this); + responseParams.add(ClientResponseContext.class, clientResponseContext); getInterceptorService().callHooks(Pointcut.CLIENT_RESPONSE, responseParams); + // Replace the contents of the response with whatever the hook returned, or the same response as before if + // it no-op'd + response = clientResponseContext.getHttpResponse(); + String mimeType; if (Constants.STATUS_HTTP_204_NO_CONTENT == response.getStatus()) { mimeType = null; diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5502-base-client-client-response-pointcut-mutate-response.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5502-base-client-client-response-pointcut-mutate-response.yaml new file mode 100644 index 00000000000..31114533369 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5502-base-client-client-response-pointcut-mutate-response.yaml @@ -0,0 +1,6 @@ +--- +type: add +issue: 5502 +jira: SMILE-7262 +title: "It is now possible to mutate an HTTP response from the CLIENT_RESPONSE Pointcut, and pass this mutated response + to downstream processing." From d597cf2763f55c55f21d9dab857f67619d5aca05 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Fri, 1 Dec 2023 15:19:51 -0500 Subject: [PATCH 13/14] Allow chained searches in Bundles where the fullUrl is fully qualified (#5529) * Allow chained searches in Bundles where the fullUrl is fully qualified * Add changelog * Spotless --- ...-in-bundle-where-fullurl-is-qualified.yaml | 4 ++ .../extractor/BaseSearchParamExtractor.java | 19 +++++- .../r4/FhirResourceDaoR4SearchNoFtTest.java | 65 ++++++++++++------- 3 files changed, 62 insertions(+), 26 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5529-allow-chained-search-in-bundle-where-fullurl-is-qualified.yaml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5529-allow-chained-search-in-bundle-where-fullurl-is-qualified.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5529-allow-chained-search-in-bundle-where-fullurl-is-qualified.yaml new file mode 100644 index 00000000000..2fdbb4cb53d --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5529-allow-chained-search-in-bundle-where-fullurl-is-qualified.yaml @@ -0,0 +1,4 @@ +--- +type: fix +issue: 5529 +title: "When using a chained SearchParameter to search within a Bundle as [described here](https://smilecdr.com/docs/fhir_storage_relational/chained_searches_and_sorts.html#document-and-message-search-parameters), if the `Bundle.entry.fullUrl` was fully qualified but the reference was not, the search did not work. This has been corrected." diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java index a4a4425071c..8ed714eb385 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java @@ -63,6 +63,7 @@ import org.hl7.fhir.instance.model.api.IBaseReference; 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.IdType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; @@ -2010,17 +2011,31 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor * references within a Bundle */ if (theAppContext instanceof IBaseBundle && isNotBlank(theUrl) && !theUrl.startsWith("#")) { + String unqualifiedVersionlessReference; + boolean isPlaceholderReference; + if (theUrl.startsWith("urn:")) { + isPlaceholderReference = true; + unqualifiedVersionlessReference = null; + } else { + isPlaceholderReference = false; + unqualifiedVersionlessReference = + new IdType(theUrl).toUnqualifiedVersionless().getValue(); + } + List entries = BundleUtil.toListOfEntries(getContext(), (IBaseBundle) theAppContext); for (BundleEntryParts next : entries) { if (next.getResource() != null) { - if (theUrl.startsWith("urn:uuid:")) { + if (isPlaceholderReference) { if (theUrl.equals(next.getUrl()) || theUrl.equals( next.getResource().getIdElement().getValue())) { return (T) next.getResource(); } } else { - if (theUrl.equals(next.getResource().getIdElement().getValue())) { + if (unqualifiedVersionlessReference.equals(next.getResource() + .getIdElement() + .toUnqualifiedVersionless() + .getValue())) { return (T) next.getResource(); } } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java index 779cd067177..826f6ffad95 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java @@ -5793,8 +5793,15 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { * [base]/Bundle?composition.patient.identifier=foo */ @ParameterizedTest - @CsvSource({"urn:uuid:5c34dc2c-9b5d-4ec1-b30b-3e2d4371508b", "Patient/ABC"}) - public void testCreateAndSearchForFullyChainedSearchParameter(String thePatientId) { + @CsvSource({ + "true , urn:uuid:5c34dc2c-9b5d-4ec1-b30b-3e2d4371508b , urn:uuid:5c34dc2c-9b5d-4ec1-b30b-3e2d4371508b", + "false, urn:uuid:5c34dc2c-9b5d-4ec1-b30b-3e2d4371508b , urn:uuid:5c34dc2c-9b5d-4ec1-b30b-3e2d4371508b", + "true , Patient/ABC , Patient/ABC ", + "false, Patient/ABC , Patient/ABC ", + "true , Patient/ABC , http://example.com/fhir/Patient/ABC ", + "false, Patient/ABC , http://example.com/fhir/Patient/ABC ", + }) + public void testCreateAndSearchForFullyChainedSearchParameter(boolean theUseFullChainInName, String thePatientId, String theFullUrl) { // Setup 1 myStorageSettings.setIndexMissingFields(JpaStorageSettings.IndexEnabledEnum.DISABLED); @@ -5819,13 +5826,18 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { composition.setSubject(new Reference(thePatientId)); Patient patient = new Patient(); - patient.setId(new IdType(thePatientId)); + patient.setId(new IdType(theFullUrl)); patient.addIdentifier().setSystem("http://foo").setValue("bar"); Bundle bundle = new Bundle(); bundle.setType(Bundle.BundleType.DOCUMENT); - bundle.addEntry().setResource(composition); - bundle.addEntry().setResource(patient); + bundle + .addEntry() + .setResource(composition); + bundle + .addEntry() + .setFullUrl(theFullUrl) + .setResource(patient); myBundleDao.create(bundle, mySrd); @@ -5833,35 +5845,40 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { bundle2.setType(Bundle.BundleType.DOCUMENT); myBundleDao.create(bundle2, mySrd); - // Verify 1 - runInTransaction(() -> { + // Test + + SearchParameterMap map; + if (theUseFullChainInName) { + map = SearchParameterMap.newSynchronous("composition.patient.identifier", new TokenParam("http://foo", "bar")); + } else { + map = SearchParameterMap.newSynchronous("composition", new ReferenceParam("patient.identifier", "http://foo|bar")); + } + IBundleProvider outcome = myBundleDao.search(map, mySrd); + + // Verify + + List params = extractAllTokenIndexes(); + assertThat(params.toString(), params, containsInAnyOrder( + "composition.patient.identifier http://foo|bar" + )); + assertEquals(1, outcome.size()); + } + + private List extractAllTokenIndexes() { + List params = runInTransaction(() -> { logAllTokenIndexes(); - List params = myResourceIndexedSearchParamTokenDao + return myResourceIndexedSearchParamTokenDao .findAll() .stream() .filter(t -> t.getParamName().contains(".")) .map(t -> t.getParamName() + " " + t.getSystem() + "|" + t.getValue()) .toList(); - assertThat(params.toString(), params, containsInAnyOrder( - "composition.patient.identifier http://foo|bar" - )); }); - - // Test 2 - IBundleProvider outcome; - - SearchParameterMap map = SearchParameterMap - .newSynchronous("composition.patient.identifier", new TokenParam("http://foo", "bar")); - outcome = myBundleDao.search(map, mySrd); - assertEquals(1, outcome.size()); - - map = SearchParameterMap - .newSynchronous("composition", new ReferenceParam("patient.identifier", "http://foo|bar")); - outcome = myBundleDao.search(map, mySrd); - assertEquals(1, outcome.size()); + return params; } + @Nested public class TagBelowTests { From 113cf4002d91408f13741d7c69ef2bf25e66a76d Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 1 Dec 2023 17:00:05 -0500 Subject: [PATCH 14/14] First commit with very rough partial solution and test. --- .../ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTask.java b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTask.java index c7616bde567..702430aa4b6 100644 --- a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTask.java +++ b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTask.java @@ -44,7 +44,7 @@ public abstract class BaseTableColumnTask extends BaseTableTask { } public BaseTableColumnTask setColumnName(String theColumnName) { - myColumnName = theColumnName.toUpperCase(); + myColumnName = theColumnName.toLowerCase(); return this; }