From 765fedfefab9a9e37d57935ae29278a772214025 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Thu, 9 Feb 2023 09:35:33 -0500 Subject: [PATCH] Additional tweaks for #4509 (#4510) * Additional tweaks for #4509 * More teaks * format imports --- .../ips/generator/IpsGeneratorSvcImpl.java | 85 +++++++---- .../jpa/ips/generator/IpsGenerationTest.java | 79 +++++++++-- .../generator/IpsGeneratorSvcImplTest.java | 132 +++++++++++++----- .../resources/tiny-patient-everything.json.gz | Bin 0 -> 1796 bytes hapi-fhir-jpaserver-uhnfhirtest/pom.xml | 17 ++- .../ca/uhn/fhirtest/TestRestfulServer.java | 2 + .../ca/uhn/fhirtest/config/TestR4Config.java | 20 +++ 7 files changed, 254 insertions(+), 81 deletions(-) create mode 100644 hapi-fhir-jpaserver-ips/src/test/resources/tiny-patient-everything.json.gz diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImpl.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImpl.java index 62d573b55ea..e85d1241b28 100644 --- a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImpl.java +++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImpl.java @@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.ips.generator; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.fhirpath.IFhirPathEvaluationContext; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; @@ -32,6 +33,7 @@ import ca.uhn.fhir.jpa.ips.api.SectionRegistry; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.dstu2.resource.Observation; import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; import ca.uhn.fhir.narrative.CustomThymeleafNarrativeGenerator; import ca.uhn.fhir.rest.api.server.IBundleProvider; @@ -42,6 +44,10 @@ import ca.uhn.fhir.util.BundleBuilder; import ca.uhn.fhir.util.CompositionBuilder; import ca.uhn.fhir.util.ResourceReferenceInfo; import ca.uhn.fhir.util.ValidateUtil; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import org.apache.commons.collections4.BidiMap; +import org.apache.commons.collections4.bidimap.DualHashBidiMap; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseExtension; @@ -66,6 +72,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; import static ca.uhn.fhir.jpa.term.api.ITermLoaderSvc.LOINC_URI; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -214,6 +221,8 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc { nextCandidate = previouslyExistingResource; sectionResourcesToInclude.addResourceIfNotAlreadyPresent(nextCandidate, originalResourceId); + } else if (theGlobalResourcesToInclude.hasResourceWithReplacementId(originalResourceId)) { + sectionResourcesToInclude.addResourceIfNotAlreadyPresent(nextCandidate, originalResourceId); } else { IIdType id = myGenerationStrategy.massageResourceId(theIpsContext, nextCandidate); nextCandidate.setId(id); @@ -226,33 +235,6 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc { } - /* - * Update any references within the added candidates - This is important - * because we might be replacing resource IDs before including them in - * the summary, so we need to also update the references to those - * resources. - */ - for (IBaseResource nextResource : sectionResourcesToInclude.getResources()) { - List references = myFhirContext.newTerser().getAllResourceReferences(nextResource); - for (ResourceReferenceInfo nextReference : references) { - String existingReference = nextReference.getResourceReference().getReferenceElement().getValue(); - if (isNotBlank(existingReference)) { - existingReference = new IdType(existingReference).toUnqualifiedVersionless().getValue(); - String replacement = theGlobalResourcesToInclude.getIdSubstitution(existingReference); - if (isNotBlank(replacement)) { - if (!replacement.equals(existingReference)) { - nextReference.getResourceReference().setReference(replacement); - } - } else if (theGlobalResourcesToInclude.getResourceById(existingReference) == null) { - // If this reference doesn't point to something we have actually - // included in the bundle, clear the reference. - nextReference.getResourceReference().setReference(null); - nextReference.getResourceReference().setResource(null); - } - } - } - } - } if (sectionResourcesToInclude.isEmpty() && theSection.getNoInfoGenerator() != null) { @@ -266,6 +248,33 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc { sectionResourcesToInclude.addResourceIfNotAlreadyPresent(noInfoResource, id); } + /* + * Update any references within the added candidates - This is important + * because we might be replacing resource IDs before including them in + * the summary, so we need to also update the references to those + * resources. + */ + for (IBaseResource nextResource : sectionResourcesToInclude.getResources()) { + List references = myFhirContext.newTerser().getAllResourceReferences(nextResource); + for (ResourceReferenceInfo nextReference : references) { + String existingReference = nextReference.getResourceReference().getReferenceElement().getValue(); + if (isNotBlank(existingReference)) { + existingReference = new IdType(existingReference).toUnqualifiedVersionless().getValue(); + String replacement = theGlobalResourcesToInclude.getIdSubstitution(existingReference); + if (isNotBlank(replacement)) { + if (!replacement.equals(existingReference)) { + nextReference.getResourceReference().setReference(replacement); + } + } else if (theGlobalResourcesToInclude.getResourceById(existingReference) == null) { + // If this reference doesn't point to something we have actually + // included in the bundle, clear the reference. + nextReference.getResourceReference().setReference(null); + nextReference.getResourceReference().setResource(null); + } + } + } + } + addSection(theSection, theCompositionBuilder, sectionResourcesToInclude, theGlobalResourcesToInclude); } @@ -278,6 +287,9 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc { sectionBuilder.addCodeCoding(LOINC_URI, theSection.getSectionCode(), theSection.getSectionDisplay()); for (IBaseResource next : theResourcesToInclude.getResources()) { + if (ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(next) == BundleEntrySearchModeEnum.INCLUDE) { + continue; + } IBaseExtension narrativeLink = ((IBaseHasExtensions) next).addExtension(); narrativeLink.setUrl("http://hl7.org/fhir/StructureDefinition/narrativeLink"); @@ -314,7 +326,18 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc { private String determinePatientCompartmentSearchParameterName(String theResourceType) { RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(theResourceType); - return resourceDef.getSearchParamsForCompartmentName("Patient").get(0).getName(); + Set searchParams = resourceDef.getSearchParamsForCompartmentName("Patient") + .stream() + .map(RuntimeSearchParam::getName) + .collect(Collectors.toSet()); + // Prefer "patient", then "subject" then anything else + if (searchParams.contains(Observation.SP_PATIENT)) { + return Observation.SP_PATIENT; + } + if (searchParams.contains(Observation.SP_SUBJECT)) { + return Observation.SP_SUBJECT; + } + return searchParams.iterator().next(); } private void massageResourceId(IpsContext theIpsContext, IBaseResource theResource) { @@ -519,7 +542,7 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc { private final List myResources = new ArrayList<>(); private final Map myIdToResource = new HashMap<>(); - private final Map myOriginalIdToNewId = new HashMap<>(); + private final BiMap myOriginalIdToNewId = HashBiMap.create(); public List getResources() { return myResources; @@ -549,6 +572,10 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc { return getResourceById(theReference.toUnqualifiedVersionless().getValue()); } + public boolean hasResourceWithReplacementId(String theReplacementId) { + return myOriginalIdToNewId.containsValue(theReplacementId); + } + public IBaseResource getResourceById(String theReference) { return myIdToResource.get(theReference); } diff --git a/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGenerationTest.java b/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGenerationTest.java index eb140fa1881..f8d7d76ec1b 100644 --- a/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGenerationTest.java +++ b/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGenerationTest.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.ips.generator; -import ca.uhn.fhir.batch2.jobs.models.BatchResourceId; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy; import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider; @@ -9,11 +9,16 @@ import ca.uhn.fhir.jpa.ips.strategy.DefaultIpsGenerationStrategy; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test; import ca.uhn.fhir.util.ClasspathUtil; +import ca.uhn.fhir.validation.FhirValidator; +import ca.uhn.fhir.validation.ValidationResult; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Condition; import org.hl7.fhir.r4.model.MedicationStatement; import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Resource; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -22,10 +27,13 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; +import java.util.List; + import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.matchesPattern; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; @ContextConfiguration(classes = {IpsGenerationTest.IpsConfig.class}) public class IpsGenerationTest extends BaseResourceProviderR4Test { @@ -41,6 +49,7 @@ public class IpsGenerationTest extends BaseResourceProviderR4Test { @AfterEach public void afterEach() { myServer.withServer(t -> t.unregisterProvider(myIpsOperationProvider)); + myDaoConfig.setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.ALPHANUMERIC); } @@ -65,26 +74,52 @@ public class IpsGenerationTest extends BaseResourceProviderR4Test { ourLog.info("Output: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output)); // Verify - - assertEquals(37, output.getEntry().size()); - String patientId = findFirstEntryResource(output, Patient.class).getId(); + validateDocument(outcome); + assertEquals(117, output.getEntry().size()); + String patientId = findFirstEntryResource(output, Patient.class, 1).getId(); assertThat(patientId, matchesPattern("urn:uuid:.*")); - MedicationStatement medicationStatement = findFirstEntryResource(output, MedicationStatement.class); + MedicationStatement medicationStatement = findFirstEntryResource(output, MedicationStatement.class, 2); assertEquals(patientId, medicationStatement.getSubject().getReference()); assertNull(medicationStatement.getInformationSource().getReference()); } - @SuppressWarnings("unchecked") - private T findFirstEntryResource(Bundle theBundle, Class theType) { - return (T) theBundle - .getEntry() - .stream() - .filter(t -> theType.isAssignableFrom(t.getResource().getClass())) - .findFirst() - .orElseThrow() - .getResource(); + @Test + public void testGenerateTinyPatientSummary() { + myDaoConfig.setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.ANY); + + Bundle sourceData = ClasspathUtil.loadCompressedResource(myFhirContext, Bundle.class, "/tiny-patient-everything.json.gz"); + sourceData.setType(Bundle.BundleType.TRANSACTION); + for (Bundle.BundleEntryComponent nextEntry : sourceData.getEntry()) { + nextEntry.getRequest().setMethod(Bundle.HTTPVerb.PUT); + nextEntry.getRequest().setUrl(nextEntry.getResource().getIdElement().toUnqualifiedVersionless().getValue()); + } + Bundle outcome = mySystemDao.transaction(mySrd, sourceData); + ourLog.info("Created {} resources", outcome.getEntry().size()); + + Bundle output = myClient + .operation() + .onInstance("Patient/5342998") + .named(JpaConstants.OPERATION_SUMMARY) + .withNoParameters(Parameters.class) + .returnResourceType(Bundle.class) + .execute(); + ourLog.info("Output: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output)); + + // Verify + validateDocument(outcome); + assertEquals(7, output.getEntry().size()); + String patientId = findFirstEntryResource(output, Patient.class, 1).getId(); + assertThat(patientId, matchesPattern("urn:uuid:.*")); + assertEquals(patientId, findEntryResource(output, Condition.class, 0, 2).getSubject().getReference()); + assertEquals(patientId, findEntryResource(output, Condition.class, 1, 2).getSubject().getReference()); } + private void validateDocument(Bundle theOutcome) { + FhirValidator validator = myFhirContext.newValidator(); + validator.registerValidatorModule(new FhirInstanceValidator(myFhirContext)); + ValidationResult validation = validator.validateWithResult(theOutcome); + assertTrue(validation.isSuccessful(), () -> myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(validation.toOperationOutcome())); + } @Configuration public static class IpsConfig { @@ -107,5 +142,21 @@ public class IpsGenerationTest extends BaseResourceProviderR4Test { } + @SuppressWarnings("unchecked") + private static T findFirstEntryResource(Bundle theBundle, Class theType, int theExpectedCount) { + return findEntryResource(theBundle, theType, 0, theExpectedCount); + } + + @SuppressWarnings("unchecked") + static T findEntryResource(Bundle theBundle, Class theType, int index, int theExpectedCount) { + List resources = theBundle + .getEntry() + .stream() + .map(Bundle.BundleEntryComponent::getResource) + .filter(r -> theType.isAssignableFrom(r.getClass())) + .toList(); + assertEquals(theExpectedCount, resources.size()); + return (T) resources.get(index); + } } diff --git a/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImplTest.java b/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImplTest.java index 12ef04f993d..efafd666eb6 100644 --- a/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImplTest.java +++ b/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImplTest.java @@ -33,6 +33,7 @@ import org.hl7.fhir.r4.model.DateTimeType; import org.hl7.fhir.r4.model.Device; import org.hl7.fhir.r4.model.DeviceUseStatement; import org.hl7.fhir.r4.model.DiagnosticReport; +import org.hl7.fhir.r4.model.Encounter; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Immunization; import org.hl7.fhir.r4.model.Medication; @@ -60,12 +61,15 @@ import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +import static ca.uhn.fhir.jpa.ips.generator.IpsGenerationTest.findEntryResource; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.startsWith; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -74,6 +78,8 @@ public class IpsGeneratorSvcImplTest { public static final String MEDICATION_ID = "Medication/tyl"; public static final String MEDICATION_STATEMENT_ID = "MedicationStatement/meds"; public static final String MEDICATION_STATEMENT_ID2 = "MedicationStatement/meds2"; + public static final String PATIENT_ID = "Patient/123"; + public static final String ENCOUNTER_ID = "Encounter/encounter"; private static final List> RESOURCE_TYPES = Lists.newArrayList( AllergyIntolerance.class, CarePlan.class, @@ -157,7 +163,7 @@ public class IpsGeneratorSvcImplTest { registerRemainingResourceDaos(); // Test - Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType("Patient/123")); + Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID)); // Verify Bundle Contents List contentResourceTypes = toEntryResourceTypeStrings(outcome); @@ -170,12 +176,7 @@ public class IpsGeneratorSvcImplTest { // Verify Composition compositions = (Composition) outcome.getEntry().get(0).getResource(); - Composition.SectionComponent section = compositions - .getSection() - .stream() - .filter(t -> t.getTitle().equals(myStrategy.getSectionRegistry().getSection(IpsSectionEnum.MEDICATION_SUMMARY).getTitle())) - .findFirst() - .orElseThrow(); + Composition.SectionComponent section = findSection(compositions, IpsSectionEnum.MEDICATION_SUMMARY); HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString()); ourLog.info("Narrative:\n{}", narrativeHtml.asXml()); @@ -191,6 +192,17 @@ public class IpsGeneratorSvcImplTest { assertThat(row.getCell(4).asNormalizedText(), containsString("2023")); } + @Nonnull + private Composition.SectionComponent findSection(Composition compositions, IpsSectionEnum sectionEnum) { + Composition.SectionComponent section = compositions + .getSection() + .stream() + .filter(t -> t.getTitle().equals(myStrategy.getSectionRegistry().getSection(sectionEnum).getTitle())) + .findFirst() + .orElseThrow(); + return section; + } + @Test public void testMedicationSummary_DuplicateSecondaryResources() { myStrategy.setSectionRegistry(new SectionRegistry().addGlobalCustomizer(t -> t.withNoInfoGenerator(null))); @@ -209,7 +221,7 @@ public class IpsGeneratorSvcImplTest { registerRemainingResourceDaos(); // Test - Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType("Patient/123")); + Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID)); // Verify Bundle Contents List contentResourceTypes = toEntryResourceTypeStrings(outcome); @@ -248,7 +260,7 @@ public class IpsGeneratorSvcImplTest { registerRemainingResourceDaos(); // Test - Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType("Patient/123")); + Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID)); // Verify Bundle Contents List contentResourceTypes = toEntryResourceTypeStrings(outcome); @@ -263,12 +275,7 @@ public class IpsGeneratorSvcImplTest { // Verify narrative - should have 2 rows (one for each primary MedicationStatement) Composition compositions = (Composition) outcome.getEntry().get(0).getResource(); - Composition.SectionComponent section = compositions - .getSection() - .stream() - .filter(t -> t.getTitle().equals(myStrategy.getSectionRegistry().getSection(IpsSectionEnum.MEDICATION_SUMMARY).getTitle())) - .findFirst() - .orElseThrow(); + Composition.SectionComponent section = findSection(compositions, IpsSectionEnum.MEDICATION_SUMMARY); HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString()); ourLog.info("Narrative:\n{}", narrativeHtml.asXml()); @@ -304,16 +311,11 @@ public class IpsGeneratorSvcImplTest { registerRemainingResourceDaos(); // Test - Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType("Patient/123")); + Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID)); // Verify Composition compositions = (Composition) outcome.getEntry().get(0).getResource(); - Composition.SectionComponent section = compositions - .getSection() - .stream() - .filter(t -> t.getTitle().equals(myStrategy.getSectionRegistry().getSection(IpsSectionEnum.MEDICAL_DEVICES).getTitle())) - .findFirst() - .orElseThrow(); + Composition.SectionComponent section = findSection(compositions, IpsSectionEnum.MEDICAL_DEVICES); HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString()); ourLog.info("Narrative:\n{}", narrativeHtml.asXml()); @@ -356,16 +358,11 @@ public class IpsGeneratorSvcImplTest { registerRemainingResourceDaos(); // Test - Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType("Patient/123")); + Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID)); // Verify Composition compositions = (Composition) outcome.getEntry().get(0).getResource(); - Composition.SectionComponent section = compositions - .getSection() - .stream() - .filter(t -> t.getTitle().equals(myStrategy.getSectionRegistry().getSection(IpsSectionEnum.IMMUNIZATIONS).getTitle())) - .findFirst() - .orElseThrow(); + Composition.SectionComponent section = findSection(compositions, IpsSectionEnum.IMMUNIZATIONS); HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString()); ourLog.info("Narrative:\n{}", narrativeHtml.asXml()); @@ -383,10 +380,82 @@ public class IpsGeneratorSvcImplTest { assertThat(row.getCell(6).asNormalizedText(), containsString("2023")); } + + @Test + public void testReferencesUpdatedInSecondaryInclusions() { + // Setup Patient + registerPatientDaoWithRead(); + + // Setup Medication + MedicationStatement + Encounter encounter = new Encounter(); + encounter.setId(new IdType(ENCOUNTER_ID)); + encounter.setSubject(new Reference(PATIENT_ID)); + ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(encounter, BundleEntrySearchModeEnum.INCLUDE); + + Condition conditionActive = new Condition(); + conditionActive.setId("Condition/conditionActive"); + conditionActive.getClinicalStatus().addCoding() + .setSystem("http://terminology.hl7.org/CodeSystem/condition-clinical") + .setCode("active"); + conditionActive.setSubject(new Reference(PATIENT_ID)); + conditionActive.setEncounter(new Reference(ENCOUNTER_ID)); + ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(conditionActive, BundleEntrySearchModeEnum.MATCH); + + Condition conditionResolved = new Condition(); + conditionResolved.setId("Condition/conditionResolved"); + conditionResolved.getClinicalStatus().addCoding() + .setSystem("http://terminology.hl7.org/CodeSystem/condition-clinical") + .setCode("resolved"); + conditionResolved.setSubject(new Reference(PATIENT_ID)); + conditionResolved.setEncounter(new Reference(ENCOUNTER_ID)); + ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(conditionResolved, BundleEntrySearchModeEnum.MATCH); + + // Conditions will be loaded from two sections (problem list and illness history) so + // we return an active condition the first time and a resolved one the second + IFhirResourceDao conditionDao = registerResourceDaoWithNoData(Condition.class); + when(conditionDao.search(any(), any())).thenReturn( + new SimpleBundleProvider(Lists.newArrayList(conditionActive, encounter)), + new SimpleBundleProvider(Lists.newArrayList(conditionResolved, encounter)) + ); + + registerRemainingResourceDaos(); + + // Test + Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID)); + + // Verify cross-references + Patient addedPatient = findEntryResource(outcome, Patient.class, 0, 1); + assertThat(addedPatient.getId(), startsWith("urn:uuid:")); + Condition addedCondition = findEntryResource(outcome, Condition.class, 0, 2); + assertThat(addedCondition.getId(), startsWith("urn:uuid:")); + Condition addedCondition2 = findEntryResource(outcome, Condition.class, 1, 2); + assertThat(addedCondition2.getId(), startsWith("urn:uuid:")); + Encounter addedEncounter = findEntryResource(outcome, Encounter.class, 0, 1); + assertThat(addedEncounter.getId(), startsWith("urn:uuid:")); + MedicationStatement addedMedicationStatement = findEntryResource(outcome, MedicationStatement.class, 0, 1); + assertThat(addedMedicationStatement.getId(), startsWith("urn:uuid:")); + assertEquals("no-medication-info", addedMedicationStatement.getMedicationCodeableConcept().getCodingFirstRep().getCode()); + assertEquals(addedPatient.getId(), addedCondition.getSubject().getReference()); + assertEquals(addedEncounter.getId(), addedCondition.getEncounter().getReference()); + assertEquals(addedPatient.getId(), addedEncounter.getSubject().getReference()); + assertEquals(addedPatient.getId(), addedMedicationStatement.getSubject().getReference()); + + // Verify sections + ourLog.info("Resource: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); + verify(conditionDao, times(2)).search(any(), any()); + Composition composition = (Composition) outcome.getEntry().get(0).getResource(); + Composition.SectionComponent problemListSection = findSection(composition, IpsSectionEnum.PROBLEM_LIST); + assertEquals(addedCondition.getId(), problemListSection.getEntry().get(0).getReference()); + assertEquals(1, problemListSection.getEntry().size()); + Composition.SectionComponent illnessHistorySection = findSection(composition, IpsSectionEnum.ILLNESS_HISTORY); + assertEquals(addedCondition2.getId(), illnessHistorySection.getEntry().get(0).getReference()); + assertEquals(1, illnessHistorySection.getEntry().size()); + } + private void registerPatientDaoWithRead() { IFhirResourceDao patientDao = registerResourceDaoWithNoData(Patient.class); Patient patient = new Patient(); - patient.setId("Patient/123"); + patient.setId(PATIENT_ID); when(patientDao.read(any(), any())).thenReturn(patient); } @@ -432,12 +501,11 @@ public class IpsGeneratorSvcImplTest { @Nonnull private static List toEntryResourceTypeStrings(Bundle outcome) { - List contentResourceTypes = outcome + return outcome .getEntry() .stream() .map(t -> t.getResource().getResourceType().name()) .collect(Collectors.toList()); - return contentResourceTypes; } @Nonnull diff --git a/hapi-fhir-jpaserver-ips/src/test/resources/tiny-patient-everything.json.gz b/hapi-fhir-jpaserver-ips/src/test/resources/tiny-patient-everything.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..92194281a39403582ed2f2e4ee245731acd8d54d GIT binary patch literal 1796 zcmV+f2mAORiwFor$=_oF19WL_c`a~ZbZKR7bS-6eWpa6RXlZU|E^2dcZUF6BTW{Mo z6n^io5VXB*wy3KuBh8RY+oW5PCUpa>YcbFgWwE10gQSwUMgIFJN|tF`c9O*n60Kpis zzBDaY_hc2|(AXMmzdhJFJK8a}tikq{CHHJ^2{<5VZtELH$B2nZzgQ`Bivf)%@(+_g z@%CXXtAG=X*p?+Pi(9tsIIioJ*OrniWq0COmXX(jTYL<&?LT0KjAJUBSvsQbYz>%I zE$*c#%OI{-)bBnVVPYJ^IIr(7+1i(BG-zTBFcay5MGzP#k)Q*_3mqAvC3Yj3uhM#n2FQ!4!fkVQu>b zB)QUEWx4jM#G;ack-!&)kYG8g?`Cj>7X3_$OimQjVM6n42z6USsU=fe%p=J^Nb-zn zd~7f%5J3?gEoz^II!+nGaDmA{lqmmBO!oWv^W<_MCRUQ-pjsC)@u z01EhmU?u>|4qQ<#WlrYjCx7AkvHo6gl)RDR65RSV^r)=3q+>ufio zF)G6oa-kGf4~4rj>>kZxTy&D<0=v%ftr)i6y2!D*rkfeoy$!>Hz5{wL==q&?H}Kap zZ2J21!He+S>+`eMboa#hHVk_(DM%4zMVzbXA(H)rBS_v(t!o}dG?q`)g+6Rz2Z7iK zXOz(4q@9wE@?=kT$6F<6Mbjd+rq;B;N;@iT6(!V5*E1Z?{FQkb(zz%3-F)Sl1W??@ zSwbxyiDpl1FYr46g!8L0=3@dCmi93mW|U(-=dD(*s=mvDIz{keZYj}u-%wUX;`n|5 zJOG@Pp-AAzH$`!z$!N7GZ9J6gZrAl}0Nj+*xhwc*F3|5(`1Ao&lxJHbT&_kJ&V@#>Rzc23S4$f+I-5}awz&ik|}p*V{ch(>GBt}8L+ zuWZg;dGIi#!f`RCNXEK}d{@cer)70B3&-sx?I=l*nk|a4yN^fJ=mjH#aySA zsOEcI}N~ z*M7q89Cm$~lh*2?aFyq6v*L%n&gyu}BR_h!LgolqP%1pyMLrkZgKNd{_)` z9wl1EMq`>4elEW?n76pPn71#dUQn+KTEXI~R?*@O2_-l(=1Xg88)miBLR!mtI_uf~ zTr*91Z!|g>YVA>H>~WJ5I15jzrI##o*?F_+?1bPh$$7jNqK%z?E5@x5(D=bAd;qew z{hsT$JK_2dPQknLi-W^U!cILr*@wpu^_3#Z8U8F4wI$%Kl25pGp}tr&zSlno0N9N$ z=4$S*-bpU7)N#XSit5vad9LgkZUCAnN`}K)36rE5lSxbMX|O-8X!Y(V_-`~BNFRdD zO$M5_@3C2*ruqk&2<)$!>H&C~>hF(Tf1}v-pRoI*PWATFRR1VbJ$RbxS7xW{f1gwR mGVOLYO1qsW+WnEgVSLT_p=tD*ym+1av$A literal 0 HcmV?d00001 diff --git a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml index f6180eaca46..49e2221ba3a 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml @@ -1,5 +1,5 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 @@ -48,11 +48,16 @@ ${project.version} classes - - ca.uhn.hapi.fhir - hapi-fhir-server-openapi - ${project.version} - + + ca.uhn.hapi.fhir + hapi-fhir-server-openapi + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-jpaserver-ips + ${project.version} + com.helger diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java index 1cf2d86b133..f1eb9ff5465 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java @@ -12,6 +12,7 @@ import ca.uhn.fhir.jpa.bulk.export.provider.BulkDataExportProvider; import ca.uhn.fhir.jpa.delete.ThreadSafeResourceDeleterSvc; import ca.uhn.fhir.jpa.graphql.GraphQLProvider; import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor; +import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider; import ca.uhn.fhir.jpa.provider.DiffProvider; import ca.uhn.fhir.jpa.provider.JpaCapabilityStatementProvider; import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2; @@ -152,6 +153,7 @@ public class TestRestfulServer extends RestfulServer { setServerConformanceProvider(confProvider); providers.add(myAppCtx.getBean(TerminologyUploaderProvider.class)); providers.add(myAppCtx.getBean(GraphQLProvider.class)); + providers.add(myAppCtx.getBean(IpsOperationProvider.class)); break; } case "R4B": { 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 48821cd3ecd..2eb94a15a68 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 @@ -2,9 +2,15 @@ package ca.uhn.fhirtest.config; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.config.HapiJpaConfig; import ca.uhn.fhir.jpa.config.r4.JpaR4Config; import ca.uhn.fhir.jpa.config.util.HapiEntityManagerFactoryUtil; +import ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy; +import ca.uhn.fhir.jpa.ips.generator.IIpsGeneratorSvc; +import ca.uhn.fhir.jpa.ips.generator.IpsGeneratorSvcImpl; +import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider; +import ca.uhn.fhir.jpa.ips.strategy.DefaultIpsGenerationStrategy; import ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect; import ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect; import ca.uhn.fhir.jpa.model.entity.ModelConfig; @@ -196,5 +202,19 @@ public class TestR4Config { return new PropertySourcesPlaceholderConfigurer(); } + @Bean + public IIpsGenerationStrategy ipsGenerationStrategy() { + return new DefaultIpsGenerationStrategy(); + } + + @Bean + public IIpsGeneratorSvc ipsGeneratorSvc(FhirContext theFhirContext, IIpsGenerationStrategy theGenerationStrategy, DaoRegistry theDaoRegistry) { + return new IpsGeneratorSvcImpl(theFhirContext, theGenerationStrategy, theDaoRegistry); + } + + @Bean + public IpsOperationProvider ipsOperationProvider(IIpsGeneratorSvc theIpsGeneratorSvc) { + return new IpsOperationProvider(theIpsGeneratorSvc); + } }