diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java index 81a578ecc23a..129b3c30bfc3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java @@ -399,7 +399,7 @@ public void wereBack() { myErrorHandler.containedResourceWithNoId(null); } else { if (!res.getId().isLocal()) { - res.setId(new IdDt('#' + res.getId().getIdPart())); + res.setId(new IdDt(res.getId().getIdPart())); } getPreResourceState().getContainedResources().put(res.getId().getValueAsString(), res); } @@ -439,7 +439,7 @@ public void wereBack() { // need an ID to be referred to) myErrorHandler.containedResourceWithNoId(null); } else { - res.getIdElement().setValue('#' + res.getIdElement().getIdPart()); + res.getIdElement().setValue(res.getIdElement().getIdPart()); getPreResourceState() .getContainedResources() .put(res.getIdElement().getValue(), res); @@ -1238,12 +1238,13 @@ void weaveContainedResources() { String ref = nextRef.getReferenceElement().getValue(); if (isNotBlank(ref)) { if (ref.startsWith("#") && ref.length() > 1) { - IBaseResource target = myContainedResources.get(ref); + String hashLessId = ref.substring(1); + IBaseResource target = myContainedResources.get(hashLessId); if (target != null) { - ourLog.debug("Resource contains local ref {}", ref); + ourLog.debug("Resource contains local ref {}", hashLessId); nextRef.setResource(target); } else { - myErrorHandler.unknownReference(null, ref); + myErrorHandler.unknownReference(null, hashLessId); } } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java index 2a3908fa8993..68a0ea387008 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java @@ -38,6 +38,7 @@ import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; import ca.uhn.fhir.model.base.composite.BaseContainedDt; import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; +import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.parser.DataFormatException; import com.google.common.collect.Lists; @@ -1428,6 +1429,25 @@ public boolean acceptUndeclaredExtension( }); } + private boolean isInternalFragment(IBaseReference theReference) { + assert theReference.getResource() != null; + if (theReference.getResource().getIdElement().isEmpty()) { + return true; + } + // Check if the reference is an external reference + // TODO Fix Hack: ensure that this conforms to what FHIR considers (URLs, URNs, and more) + if (theReference.getResource().getIdElement().isAbsolute() + || theReference.getResource().getIdElement().getValueAsString().startsWith("urn:")) { + return false; + } + // Check if the reference is a FHIR resource resolvable on the local server + // TODO Fix Hack: check for all resource types, not just Patient + if (theReference.getResource().getIdElement().getValueAsString().startsWith("Patient/")) { + return false; + } + return true; + } + private void containResourcesForEncoding( ContainedResources theContained, IBaseResource theResource, boolean theModifyResource) { List allReferences = getAllPopulatedChildElementsOfType(theResource, IBaseReference.class); @@ -1456,7 +1476,7 @@ private void containResourcesForEncoding( for (IBaseReference next : allReferences) { IBaseResource resource = next.getResource(); if (resource != null) { - if (resource.getIdElement().isEmpty() || resource.getIdElement().isLocal()) { + if (resource.getIdElement().isEmpty() || isInternalFragment(next)) { IIdType id = theContained.addContained(resource); if (id == null) { @@ -1464,9 +1484,9 @@ private void containResourcesForEncoding( } if (theModifyResource) { getContainedResourceList(theResource).add(resource); - next.setReference(id.getValue()); + next.setReference("#" + id.getValue()); } - if (resource.getIdElement().isLocal() && theContained.hasExistingIdToContainedResource()) { + if (isInternalFragment(next) && theContained.hasExistingIdToContainedResource()) { theContained .getExistingIdToContainedResource() .remove(resource.getIdElement().getValue()); @@ -1508,13 +1528,6 @@ public ContainedResources containResources(IBaseResource theResource, OptionsEnu List containedResources = getContainedResourceList(theResource); for (IBaseResource next : containedResources) { - String nextId = next.getIdElement().getValue(); - if (StringUtils.isNotBlank(nextId)) { - if (!nextId.startsWith("#")) { - nextId = '#' + nextId; - } - next.getIdElement().setValue(nextId); - } contained.addContained(next); } @@ -1810,14 +1823,20 @@ public IIdType addContained(IBaseResource theResource) { return existing; } - IIdType newId = theResource.getIdElement(); + final IIdType newId = new IdDt(theResource.getIdElement()); if (isBlank(newId.getValue())) { - newId.setValue("#" + UUID.randomUUID()); + UUID randomUUID = UUID.randomUUID(); + theResource.getIdElement().setValue(randomUUID.toString()); + newId.setValue("#" + randomUUID); + // TODO put newId in resourceToIdMap ? + getResourceToIdMap().put(theResource, newId); + } else { + // TODO put new IdDt with # in resourceToIdMap ? + getResourceToIdMap().put(theResource, new IdDt("#" + newId.getIdPart())); } - - getResourceToIdMap().put(theResource, newId); + // getResourceToIdMap().put(theResource, newId); getOrCreateResourceList().add(theResource); - return newId; + return theResource.getIdElement(); } public void addContained(IIdType theId, IBaseResource theResource) { diff --git a/hapi-fhir-jpaserver-hfql/src/test/java/ca/uhn/fhir/jpa/fql/executor/HfqlExecutorTest.java b/hapi-fhir-jpaserver-hfql/src/test/java/ca/uhn/fhir/jpa/fql/executor/HfqlExecutorTest.java index a2fca4ee0fb2..337e55fa45ec 100644 --- a/hapi-fhir-jpaserver-hfql/src/test/java/ca/uhn/fhir/jpa/fql/executor/HfqlExecutorTest.java +++ b/hapi-fhir-jpaserver-hfql/src/test/java/ca/uhn/fhir/jpa/fql/executor/HfqlExecutorTest.java @@ -198,7 +198,7 @@ select foo() IHfqlExecutionResult result = myHfqlExecutor.executeInitialSearch(statement, null, mySrd); IHfqlExecutionResult.Row row = result.getNextRow(); assertEquals(IHfqlExecutionResult.ROW_OFFSET_ERROR, row.getRowOffset()); - assertEquals("Failed to evaluate FHIRPath expression \"foo()\". Error: HAPI-2404: Error in ?? at 1, 1: The name foo is not a valid function name", row.getRowValues().get(0)); + assertEquals("Failed to evaluate FHIRPath expression \"foo()\". Error: HAPI-2404: Error @1, 1: The name foo is not a valid function name", row.getRowValues().get(0)); assertFalse(result.hasNext()); } 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 6c23590d2c8b..6636d60158ca 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 @@ -50,6 +50,7 @@ import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.primitive.BoundCodeDt; +import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.param.DateParam; @@ -2237,7 +2238,7 @@ private void extractResourceLinkFromReference( IBaseReference valueRef) { IIdType nextId = valueRef.getReferenceElement(); if (nextId.isEmpty() && valueRef.getResource() != null) { - nextId = valueRef.getResource().getIdElement(); + nextId = new IdDt("#" + valueRef.getResource().getIdElement()); } if (nextId == null || nextId.isEmpty()) { diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java index 80ef7ed53de2..6e56e640552c 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java @@ -208,5 +208,10 @@ public boolean conformsToProfile(FHIRPathEngine engine, Object appContext, Base public ValueSet resolveValueSet(FHIRPathEngine engine, Object appContext, String url) { return null; } + + @Override + public boolean paramIsType(String name, int index) { + return false; + } } } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java index 1d177e53d5a1..6f67fdfd9e85 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java @@ -462,7 +462,8 @@ private void extractSearchIndexParametersForTargetResources( private IBaseResource findContainedResource(Collection resources, IBaseReference reference) { for (IBaseResource resource : resources) { - if (resource.getIdElement().equals(reference.getReferenceElement())) return resource; + if (("#" + resource.getIdElement()) + .equals(reference.getReferenceElement().toString())) return resource; } return null; } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/JpaPersistedResourceValidationSupportFromValidationChainTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/JpaPersistedResourceValidationSupportFromValidationChainTest.java index 6163dea83f8d..f06c8567c0ea 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/JpaPersistedResourceValidationSupportFromValidationChainTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/JpaPersistedResourceValidationSupportFromValidationChainTest.java @@ -77,7 +77,7 @@ public void validation_Jpa_Bundle_MeasureReferencesLibraryAndLibrary() { final ValidationResult validationResult = validator.validateWithResult(bundleWithBadLibrary); - assertEquals(12, validationResult.getMessages().stream().filter(errorMessagePredicate()).count()); + assertEquals(10, validationResult.getMessages().stream().filter(errorMessagePredicate()).count()); } @Test @@ -94,7 +94,7 @@ public void validation_Jpa_Bundle_MeasureOnly() { final ValidationResult validationResult = validator.validateWithResult(bundleWithMeasureOnly ); - assertEquals(10, validationResult.getMessages().stream().filter(errorMessagePredicate()).count()); + assertEquals(8, validationResult.getMessages().stream().filter(errorMessagePredicate()).count()); } @Test @@ -108,7 +108,7 @@ public void validation_Jpa_Bundle_MeasureOnly_NoLibraryReference() { final ValidationResult validationResult = validator.validateWithResult(bundleWithMeasureOnlyNoLibraryReference); - assertEquals(9, validationResult.getMessages().stream().filter(errorMessagePredicate()).count()); + assertEquals(7, validationResult.getMessages().stream().filter(errorMessagePredicate()).count()); } @Test diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoSearchParameterTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoSearchParameterTest.java index ce29bd181cbf..62b9c97255a5 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoSearchParameterTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoSearchParameterTest.java @@ -94,7 +94,7 @@ public void testValidateInvalidExpression() { myDao.validateResourceForStorage(nextSearchParameter, null); fail(); } catch (UnprocessableEntityException e) { - assertEquals(Msg.code(1121) + "Invalid FHIRPath format for SearchParameter.expression \"Patient.ex[[[\": Error in ?? at 1, 1: Found [ expecting a token name", e.getMessage()); + assertEquals(Msg.code(1121) + "Invalid FHIRPath format for SearchParameter.expression \"Patient.ex[[[\": Error @1, 1: Found [ expecting a token name", e.getMessage()); } } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java index c97ff21fc753..4cbee81e6711 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java @@ -715,7 +715,7 @@ public void testShouldResolveAThreeLinkChainWithAContainedResourceAtTheEndOfTheC obs.getCode().addCoding().setCode("obs2").setSystem("Some System").setDisplay("Body weight as measured by me"); obs.setStatus(Observation.ObservationStatus.FINAL); obs.setValue(new Quantity(81)); - obs.setSubject(new Reference(p.getId())); + obs.setSubject(new Reference("#" +p.getId())); obs.setEncounter(new Reference(encounter.getId())); oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); @@ -1024,6 +1024,7 @@ public void testShouldResolveAThreeLinkChainWithQualifiersWithAContainedResource Patient p = new Patient(); p.setId("pat"); + //p.getContained().add(org); p.addName().setFamily("Smith").addGiven("John"); p.getManagingOrganization().setReference(org.getId()); @@ -1035,6 +1036,7 @@ public void testShouldResolveAThreeLinkChainWithQualifiersWithAContainedResource oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); Device d = new Device(); + //d.getContained().add(org); d.setId("dev"); d.getOwner().setReference(org.getId()); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java index 67105890219f..7badf1e99b68 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java @@ -168,7 +168,7 @@ public void testCreateLinkCreatesAppropriatePaths_ContainedResource() { myPatientDao.update(p, mySrd); Observation containedObs = new Observation(); - containedObs.setId("#cont"); + containedObs.setId("cont"); containedObs.setSubject(new Reference("Patient/A")); Encounter enc = new Encounter(); @@ -285,7 +285,7 @@ public void testCreateLinkCreatesAppropriatePaths_ContainedResourceRecursive_ToO p.setManagingOrganization(new Reference("Organization/ABC")); Observation containedObs = new Observation(); - containedObs.setId("#cont"); + containedObs.setId("cont"); containedObs.setSubject(new Reference("#pat")); Encounter enc = new Encounter(); @@ -754,7 +754,7 @@ public void testTagsInContainedResourcesPreserved() { assertEquals(organizationUuid, organizationUuidParsed); Organization org = (Organization) p.getManagingOrganization().getResource(); - assertEquals("#" + organizationUuid, org.getId()); + assertEquals(organizationUuid, org.getId()); assertThat(org.getMeta().getTag()).hasSize(1); } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java index e215822d067e..b5047713ce16 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java @@ -853,7 +853,7 @@ public void testValidateProfileTargetType_PolicyCheckExistsAndType() throws IOEx obs.setSubject(new Reference("Group/ABC")); oo = validateAndReturnOutcome(obs); ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); - assertThat(oo.getIssueFirstRep().getDiagnostics()).as(encode(oo)).isEqualTo("Unable to find a match for profile Group/ABC (by type) among choices: ; [CanonicalType[http://hl7.org/fhir/StructureDefinition/Patient]]"); + assertThat(oo.getIssueFirstRep().getDiagnostics()).as(encode(oo)).isEqualTo("Unable to find a match for profile Group/ABC (by type) among choices: ; [http://hl7.org/fhir/StructureDefinition/Patient]"); // Target of right type obs.setSubject(new Reference("Patient/DEF")); 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 eaed24c66dbb..d958c32de2f8 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 @@ -195,6 +195,7 @@ import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; import org.hl7.fhir.utilities.validation.ValidationMessage; +import org.hl7.fhir.validation.instance.advisor.BasePolicyAdvisorForFullValidation; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Order; @@ -1073,6 +1074,16 @@ public boolean isSuppressMessageId(String path, String messageId) { public ReferenceValidationPolicy getReferencePolicy() { return ReferenceValidationPolicy.IGNORE; } + + @Override + public IValidationPolicyAdvisor getPolicyAdvisor() { + return new BasePolicyAdvisorForFullValidation(getReferencePolicy()); + } + + @Override + public IValidationPolicyAdvisor setPolicyAdvisor(IValidationPolicyAdvisor policyAdvisor) { + return null; + } } diff --git a/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/rules/config/MdmRuleValidatorTest.java b/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/rules/config/MdmRuleValidatorTest.java index a30d7dfc8bb2..a3e4acb2fd19 100644 --- a/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/rules/config/MdmRuleValidatorTest.java +++ b/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/rules/config/MdmRuleValidatorTest.java @@ -76,7 +76,7 @@ public void testMatcherBadFhirPath() throws IOException { try { setMdmRuleJson("bad-rules-bad-fhirpath.json"); fail(); } catch (ConfigurationException e) { - assertThat(e.getMessage()).startsWith(Msg.code(1518) + "MatchField [given-name] resourceType [Patient] has failed FHIRPath evaluation. Error in ?? at 1, 1: The name blurst is not a valid function name"); + assertThat(e.getMessage()).startsWith(Msg.code(1518) + "MatchField [given-name] resourceType [Patient] has failed FHIRPath evaluation. Error @1, 1: The name blurst is not a valid function name"); } } diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/validation/ValidatorPolicyAdvisor.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/validation/ValidatorPolicyAdvisor.java index 10ad382fdd7d..b8264cb6c30d 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/validation/ValidatorPolicyAdvisor.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/validation/ValidatorPolicyAdvisor.java @@ -31,6 +31,7 @@ import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; import org.hl7.fhir.utilities.validation.ValidationMessage; +import org.hl7.fhir.validation.instance.advisor.BasePolicyAdvisorForFullValidation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -127,4 +128,14 @@ public boolean isSuppressMessageId(String path, String messageId) { public ReferenceValidationPolicy getReferencePolicy() { return ReferenceValidationPolicy.IGNORE; } + + @Override + public IValidationPolicyAdvisor getPolicyAdvisor() { + return new BasePolicyAdvisorForFullValidation(getReferencePolicy()); + } + + @Override + public IValidationPolicyAdvisor setPolicyAdvisor(IValidationPolicyAdvisor policyAdvisor) { + 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 index fb0d9c4ab5c7..0752776b0cff 100644 --- 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 @@ -15,6 +15,7 @@ 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.r5.utils.validation.ValidatorSession; import org.hl7.fhir.validation.instance.InstanceValidator; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -56,7 +57,8 @@ public void checkFetchByUrl() { InstanceValidator v = new InstanceValidator( wrappedWorkerContext, new FhirInstanceValidator.NullEvaluationContext(), - new XVerExtensionManager(null)); + new XVerExtensionManager(null), + new ValidatorSession()); RequestDetails r = new SystemRequestDetails(); // test Element returnedResource = fetcher.fetch(v, r,"http://www.test-url-for-questionnaire.com/Questionnaire/test-id|1.0.0"); diff --git a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2_1Test.java b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2_1Test.java index 4c6fea901d86..53fbfe8b1229 100644 --- a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2_1Test.java +++ b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2_1Test.java @@ -403,7 +403,7 @@ public void testEncodeAndParseContained() { assertNotNull(patient.getManagingOrganization().getResource()); org = (Organization) patient.getManagingOrganization().getResource(); - assertEquals("#" + organizationUuid, org.getIdElement().getValue()); + assertEquals( organizationUuid, org.getIdElement().getValue()); assertEquals("Contained Test Organization", org.getName()); // And re-encode a second time @@ -817,7 +817,7 @@ public void testEncodeBinaryWithNoContentType() { public void testEncodeBundleWithContained() { DiagnosticReport rpt = new DiagnosticReport(); rpt.addResult().setResource(new Observation().setCode(new CodeableConcept().setText("Sharp1")).setId("#1")); - rpt.addResult().setResource(new Observation().setCode(new CodeableConcept().setText("Uuid1")).setId("urn:uuid:UUID1")); + rpt.addResult().setResource(new Observation().setCode(new CodeableConcept().setText("Uuid1")).setId("https//example.org/UUID1")); Bundle b = new Bundle(); b.addEntry().setResource(rpt); diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java index 2388fa72413d..895a8bfeb4b4 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java @@ -585,7 +585,7 @@ public void testEncodeAndParseContained() { assertNotNull(patient.getManagingOrganization().getResource()); org = (Organization) patient.getManagingOrganization().getResource(); - assertEquals("#" + organizationUuid, org.getIdElement().getValue()); + assertEquals(organizationUuid, org.getIdElement().getValue()); assertEquals("Contained Test Organization", org.getName()); // And re-encode a second time @@ -607,7 +607,7 @@ public void testEncodeAndParseContained() { // And re-encode once more, with the references cleared and a manually set local ID patient.getContained().clear(); patient.getManagingOrganization().setReference(null); - patient.getManagingOrganization().getResource().setId(("#333")); + patient.getManagingOrganization().getResource().setId(("333")); encoded = xmlParser.encodeResourceToString(patient); ourLog.info(encoded); assertThat(encoded).contains(Arrays.asList("", "", "", "")); @@ -1324,7 +1324,7 @@ public void testEncodeContainedResources() { // Adding medication to Contained. Medication medResource = new Medication(); medResource.setCode(codeDt); - medResource.setId("#" + medId); + medResource.setId(medId); medicationPrescript.getContained().add(medResource); // Medication reference. This should point to the contained resource. @@ -2055,17 +2055,17 @@ public void testEncodeWithContained() { // Will be added by reference Patient p = new Patient(); - p.setId("#" + "1000"); + p.setId("1000"); contained.add(p); // Will be added by direct resource object Location l = new Location(); - l.setId("#" + "1001"); + l.setId("1001"); contained.add(l); // Will not be referred to (and therefore shouldn't appear in output) Location l2 = new Location(); - l2.setId("#1002"); + l2.setId("1002"); contained.add(l2); Appointment appointment = new Appointment(); @@ -3410,10 +3410,10 @@ public void testParseWovenContainedResources() throws IOException { DiagnosticReport resource = (DiagnosticReport) bundle.getEntry().get(0).getResource(); Observation obs = (Observation) resource.getResult().get(1).getResource(); - assertEquals("#2", obs.getId()); + assertEquals("2", obs.getId()); Reference performerFirstRep = obs.getPerformerFirstRep(); Practitioner performer = (Practitioner) performerFirstRep.getResource(); - assertEquals("#3", performer.getId()); + assertEquals("3", performer.getId()); } /** diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/HapiWorkerContext.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/HapiWorkerContext.java index a0f6d85a5772..c2d7285da3d4 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/HapiWorkerContext.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/HapiWorkerContext.java @@ -40,6 +40,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -488,4 +489,27 @@ public static ConceptValidationOptions convertConceptValidationOptions(Validatio public boolean isPrimitiveType(String theType) { return PRIMITIVE_TYPES.contains(theType); } + + public List fetchResourcesByType(Class theClass) { + List res = new ArrayList<>(); + if (theClass == StructureDefinition.class) { + res.addAll((Collection) getStructures()); + } + return res; + } + + public T fetchResource(Class class_, String uri, Resource source) { + return fetchResource(class_, uri); + } + + @Override + public List fetchTypeDefinitions(String n) { + List types = new ArrayList<>(); + for (StructureDefinition sd : allStructures()) { + if (n.equals(sd.getTypeTail())) { + types.add(sd); + } + } + return types; + } } diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FhirPathR4.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FhirPathR4.java index 2c04412d4b81..e7fd0f36fe59 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FhirPathR4.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FhirPathR4.java @@ -158,6 +158,11 @@ public boolean conformsToProfile(FHIRPathEngine engine, Object appContext, Base public ValueSet resolveValueSet(FHIRPathEngine engine, Object appContext, String url) { return null; } + + @Override + public boolean paramIsType(String name, int index) { + return false; + } }); } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java index 27e05d93b264..8fee9831cb7a 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java @@ -30,6 +30,7 @@ import org.hl7.fhir.r4.model.Encounter; import org.hl7.fhir.r4.model.Extension; import org.hl7.fhir.r4.model.HumanName; +import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.Medication; import org.hl7.fhir.r4.model.MedicationDispense; @@ -46,6 +47,7 @@ import org.hl7.fhir.r4.model.Quantity; import org.hl7.fhir.r4.model.QuestionnaireResponse; import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.Specimen; import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.Type; @@ -55,6 +57,8 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -76,6 +80,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -744,159 +749,8 @@ public void testPrettyPrint() { assertThat(output).contains("\n \"resourceType\""); } - /** - * See #814 - */ - @Test - public void testDuplicateContainedResourcesNotOutputtedTwice() { - MedicationDispense md = new MedicationDispense(); - - MedicationRequest mr = new MedicationRequest(); - md.addAuthorizingPrescription().setResource(mr); - - Medication med = new Medication(); - md.setMedication(new Reference(med)); - mr.setMedication(new Reference(med)); - - String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(md); - ourLog.info(encoded); - - int idx = encoded.indexOf("\"Medication\""); - assertThat(idx).isNotEqualTo(-1); - - idx = encoded.indexOf("\"Medication\"", idx + 1); - assertEquals(-1, idx); - } - - @Test - public void testDuplicateContainedResourcesAcrossABundleAreReplicated() { - Bundle b = new Bundle(); - Specimen specimen = new Specimen(); - Practitioner practitioner = new Practitioner(); - DiagnosticReport report = new DiagnosticReport(); - report.addSpecimen(new Reference(specimen)); - b.addEntry().setResource(report).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("/DiagnosticReport"); - - Observation obs = new Observation(); - obs.addPerformer(new Reference(practitioner)); - obs.setSpecimen(new Reference(specimen)); - - b.addEntry().setResource(obs).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("/Observation"); - - String encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(b); - //Then: Diag should contain one local contained specimen - assertThat(encoded).contains("[{\"resource\":{\"resourceType\":\"DiagnosticReport\",\"contained\":[{\"resourceType\":\"Specimen\",\"id\":\""+ specimen.getId().replaceFirst("#", "") +"\"}]"); - //Then: Obs should contain one local contained specimen, and one local contained pract - assertThat(encoded).contains("\"resource\":{\"resourceType\":\"Observation\",\"contained\":[{\"resourceType\":\"Specimen\",\"id\":\""+ specimen.getId().replaceFirst("#", "") +"\"},{\"resourceType\":\"Practitioner\",\"id\":\"" + practitioner.getId().replaceAll("#","") + "\"}]"); - assertThat(encoded).contains("\"performer\":[{\"reference\":\""+practitioner.getId()+"\"}],\"specimen\":{\"reference\":\""+specimen.getId()+"\"}"); - - //Also, reverting the operation should work too! - Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, encoded); - IBaseResource resource1 = ((DiagnosticReport) bundle.getEntry().get(0).getResource()).getSpecimenFirstRep().getResource(); - IBaseResource resource = ((Observation) bundle.getEntry().get(1).getResource()).getSpecimen().getResource(); - assertThat(resource1.getIdElement().getIdPart()).isEqualTo(resource.getIdElement().getIdPart()); - assertThat(resource1).isNotSameAs(resource); - - } - - @Test - public void testContainedResourcesNotAutoContainedWhenConfiguredNotToDoSo() { - MedicationDispense md = new MedicationDispense(); - md.addIdentifier().setValue("DISPENSE"); - Medication med = new Medication(); - med.getCode().setText("MED"); - md.setMedication(new Reference(med)); - ourCtx.getParserOptions().setAutoContainReferenceTargetsWithNoId(false); - String encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(md); - assertEquals("{\"resourceType\":\"MedicationDispense\",\"identifier\":[{\"value\":\"DISPENSE\"}],\"medicationReference\":{}}", encoded); - - ourCtx.getParserOptions().setAutoContainReferenceTargetsWithNoId(true); - encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(md); - String guidWithHash = med.getId(); - String withoutHash = guidWithHash.replace("#", ""); - assertThat(encoded).contains("{\"resourceType\":\"MedicationDispense\",\"contained\":[{\"resourceType\":\"Medication\",\"id\":\"" + withoutHash + "\",\"code\":{\"text\":\"MED\"}}],\"identifier\":[{\"value\":\"DISPENSE\"}],\"medicationReference\":{\"reference\":\"" + guidWithHash +"\"}}"); //Note we dont check exact ID since its a GUID - } - - @Test - public void testParseBundleWithMultipleNestedContainedResources() throws Exception { - String text = loadResource("/bundle-with-two-patient-resources.json"); - - Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, text); - assertEquals(Boolean.TRUE, bundle.getUserData(BaseParser.RESOURCE_CREATED_BY_PARSER)); - assertEquals(Boolean.TRUE, bundle.getEntry().get(0).getResource().getUserData(BaseParser.RESOURCE_CREATED_BY_PARSER)); - assertEquals(Boolean.TRUE, bundle.getEntry().get(1).getResource().getUserData(BaseParser.RESOURCE_CREATED_BY_PARSER)); - - assertEquals("12346", getPatientIdValue(bundle, 0)); - assertEquals("12345", getPatientIdValue(bundle, 1)); - } - - private String getPatientIdValue(Bundle input, int entry) { - final DocumentReference documentReference = (DocumentReference) input.getEntry().get(entry).getResource(); - final Patient patient = (Patient) documentReference.getSubject().getResource(); - return patient.getIdentifier().get(0).getValue(); - } - - /** - * See #814 - */ - @Test - public void testDuplicateContainedResourcesNotOutputtedTwiceWithManualIds() { - MedicationDispense md = new MedicationDispense(); - - MedicationRequest mr = new MedicationRequest(); - mr.setId("#MR"); - md.addAuthorizingPrescription().setResource(mr); - - Medication med = new Medication(); - med.setId("#MED"); - md.setMedication(new Reference(med)); - mr.setMedication(new Reference(med)); - - String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(md); - ourLog.info(encoded); - - int idx = encoded.indexOf("\"Medication\""); - assertThat(idx).isNotEqualTo(-1); - - idx = encoded.indexOf("\"Medication\"", idx + 1); - assertEquals(-1, idx); - - } - - /* - * See #814 - */ - @Test - public void testDuplicateContainedResourcesNotOutputtedTwiceWithManualIdsAndManualAddition() { - MedicationDispense md = new MedicationDispense(); - - MedicationRequest mr = new MedicationRequest(); - mr.setId("#MR"); - md.addAuthorizingPrescription().setResource(mr); - - Medication med = new Medication(); - med.setId("#MED"); - - Reference medRef = new Reference(); - medRef.setReference("#MED"); - md.setMedication(medRef); - mr.setMedication(medRef); - - md.getContained().add(mr); - md.getContained().add(med); - - String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(md); - ourLog.info(encoded); - - int idx = encoded.indexOf("\"Medication\""); - assertThat(idx).isNotEqualTo(-1); - - idx = encoded.indexOf("\"Medication\"", idx + 1); - assertEquals(-1, idx); - - } /** * Make sure we can perform parallel parse/encodes against the same @@ -1087,85 +941,8 @@ public void testEncodeWithInvalidExtensionContainingValueAndNestedExtensions_wit assertEquals(false, errorHandler.isErrorOnInvalidValue()); } - @Test - public void testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocalFirst() { - - Observation obs = new Observation(); - - Patient pt = new Patient(); - pt.setId("#1"); - pt.addName().setFamily("FAM"); - obs.getSubject().setReference("#1"); - obs.getContained().add(pt); - - Encounter enc = new Encounter(); - enc.setStatus(Encounter.EncounterStatus.ARRIVED); - obs.getEncounter().setResource(enc); - - String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs); - ourLog.info(encoded); - - obs = ourCtx.newJsonParser().parseResource(Observation.class, encoded); - assertEquals("#1", obs.getContained().get(0).getId()); - assertEquals(enc.getId(), obs.getContained().get(1).getId()); - - pt = (Patient) obs.getSubject().getResource(); - assertEquals("FAM", pt.getNameFirstRep().getFamily()); - - enc = (Encounter) obs.getEncounter().getResource(); - assertEquals(Encounter.EncounterStatus.ARRIVED, enc.getStatus()); - } - - @Test - public void testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocalLast() { - - Observation obs = new Observation(); - - Patient pt = new Patient(); - pt.addName().setFamily("FAM"); - obs.getSubject().setResource(pt); - - Encounter enc = new Encounter(); - enc.setId("#1"); - enc.setStatus(Encounter.EncounterStatus.ARRIVED); - obs.getEncounter().setReference("#1"); - obs.getContained().add(enc); - - String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs); - ourLog.info(encoded); - - obs = ourCtx.newJsonParser().parseResource(Observation.class, encoded); - assertEquals("#1", obs.getContained().get(0).getId()); - assertEquals(pt.getId(), obs.getContained().get(1).getId()); - - pt = (Patient) obs.getSubject().getResource(); - assertEquals("FAM", pt.getNameFirstRep().getFamily()); - - enc = (Encounter) obs.getEncounter().getResource(); - assertEquals(Encounter.EncounterStatus.ARRIVED, enc.getStatus()); - } - - @Test - public void testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocalLast2() { - - MedicationRequest mr = new MedicationRequest(); - Practitioner pract = new Practitioner().setActive(true); - mr.getRequester().setResource(pract); - - String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(mr); - ourLog.info(encoded); - mr = ourCtx.newJsonParser().parseResource(MedicationRequest.class, encoded); - - Medication med = new Medication().setStatus(Medication.MedicationStatus.ACTIVE); - mr.setMedication(new Reference(med)); - encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(mr); - ourLog.info(encoded); - mr = ourCtx.newJsonParser().parseResource(MedicationRequest.class, encoded); - assertEquals(pract.getId(), mr.getContained().get(0).getId()); - assertEquals(med.getId(), mr.getContained().get(1).getId()); - } @Test public void testExcludeNothing() { @@ -1428,7 +1205,12 @@ public void testEncodingTextSection() { assertThat(idx).isNotEqualTo(-1); } - + /** + * Enable individually if needed to test timings. + */ + @Nested + @DisplayName("Timing Tests") + class TimingTests { /** * 2019-09-19 - Pre #1489 * 18:24:48.548 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:483] - Encoded 200 passes - 50ms / pass - 19.7 / second @@ -1606,85 +1388,7 @@ private Bundle createBigBundle() { } return b; } - - /** - * Ensure that a contained bundle doesn't cause a crash - */ - @Test - public void testEncodeContainedBundle() { - String auditEvent = "{\n" + - " \"resourceType\": \"AuditEvent\",\n" + - " \"contained\": [ {\n" + - " \"resourceType\": \"Bundle\",\n" + - " \"id\": \"REASONS\",\n" + - " \"entry\": [ {\n" + - " \"resource\": {\n" + - " \"resourceType\": \"Condition\",\n" + - " \"id\": \"123\"\n" + - " }\n" + - " } ]\n" + - " }, {\n" + - " \"resourceType\": \"MeasureReport\",\n" + - " \"id\": \"MRPT5000602611RD\",\n" + - " \"evaluatedResource\": [ {\n" + - " \"reference\": \"#REASONS\"\n" + - " } ]\n" + - " } ],\n" + - " \"entity\": [ {\n" + - " \"what\": {\n" + - " \"reference\": \"#MRPT5000602611RD\"\n" + - " }\n" + - " } ]\n" + - "}"; - AuditEvent ae = ourCtx.newJsonParser().parseResource(AuditEvent.class, auditEvent); - String auditEventAsString = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(ae); - assertEquals(auditEvent, auditEventAsString); - } - - - /** - * Ensure that a contained bundle doesn't cause a crash - */ - @Test - public void testParseAndEncodePreservesContainedResourceOrder() { - String auditEvent = "{\n" + - " \"resourceType\": \"AuditEvent\",\n" + - " \"contained\": [ {\n" + - " \"resourceType\": \"Observation\",\n" + - " \"id\": \"A\",\n" + - " \"identifier\": [ {\n" + - " \"value\": \"A\"\n" + - " } ]\n" + - " }, {\n" + - " \"resourceType\": \"Observation\",\n" + - " \"id\": \"B\",\n" + - " \"identifier\": [ {\n" + - " \"value\": \"B\"\n" + - " } ]\n" + - " } ],\n" + - " \"entity\": [ {\n" + - " \"what\": {\n" + - " \"reference\": \"#B\"\n" + - " }\n" + - " }, {\n" + - " \"what\": {\n" + - " \"reference\": \"#A\"\n" + - " }\n" + - " } ]\n" + - "}"; - - ourLog.info("Input: {}", auditEvent); - AuditEvent ae = ourCtx.newJsonParser().parseResource(AuditEvent.class, auditEvent); - assertEquals("#A", ae.getContained().get(0).getId()); - assertEquals("#B", ae.getContained().get(1).getId()); - assertEquals("#B", ae.getEntity().get(0).getWhat().getReference()); - assertEquals("#A", ae.getEntity().get(1).getWhat().getReference()); - - String serialized = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(ae); - assertEquals(auditEvent, serialized); - - } - +} @Test public void testEncodeToString_PrimitiveDataType() { @@ -1877,6 +1581,437 @@ public void testPreCommentsToFhirComments() { assertThat(patientString).doesNotContain("fhir_comment"); } + @Nested + @DisplayName("Contained resources handling") + class ContainedResources { + /** + * See #814 + */ + @Test + public void testDuplicateContainedResourcesNotOutputtedTwice() { + MedicationDispense md = new MedicationDispense(); + + MedicationRequest mr = new MedicationRequest(); + md.addAuthorizingPrescription().setResource(mr); + + Medication med = new Medication(); + md.setMedication(new Reference(med)); + mr.setMedication(new Reference(med)); + + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(md); + ourLog.info(encoded); + + int idx = encoded.indexOf("\"Medication\""); + assertThat(idx).isNotEqualTo(-1); + + idx = encoded.indexOf("\"Medication\"", idx + 1); + assertEquals(-1, idx); + } + + @Test + public void testDuplicateContainedResourcesAcrossABundleAreReplicated() { + Bundle b = new Bundle(); + Specimen specimen = new Specimen(); + Practitioner practitioner = new Practitioner(); + DiagnosticReport report = new DiagnosticReport(); + report.addSpecimen(new Reference(specimen)); + b.addEntry().setResource(report).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("/DiagnosticReport"); + + Observation obs = new Observation(); + obs.addPerformer(new Reference(practitioner)); + obs.setSpecimen(new Reference(specimen)); + + b.addEntry().setResource(obs).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("/Observation"); + + String encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(b); + //Then: Diag should contain one local contained specimen + assertThat(encoded).contains("[{\"resource\":{\"resourceType\":\"DiagnosticReport\",\"contained\":[{\"resourceType\":\"Specimen\",\"id\":\""+ specimen.getId() +"\"}]"); + //Then: Obs should contain one local contained specimen, and one local contained pract + assertThat(encoded).contains("\"resource\":{\"resourceType\":\"Observation\",\"contained\":[{\"resourceType\":\"Specimen\",\"id\":\""+ specimen.getId() +"\"},{\"resourceType\":\"Practitioner\",\"id\":\"" + practitioner.getId() + "\"}]"); + assertThat(encoded).contains("\"performer\":[{\"reference\":\"#"+practitioner.getId()+"\"}],\"specimen\":{\"reference\":\"#"+specimen.getId()+"\"}"); + + //Also, reverting the operation should work too! + Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, encoded); + IBaseResource resource1 = ((DiagnosticReport) bundle.getEntry().get(0).getResource()).getSpecimenFirstRep().getResource(); + IBaseResource resource = ((Observation) bundle.getEntry().get(1).getResource()).getSpecimen().getResource(); + assertThat(resource1.getIdElement().getIdPart()).isEqualTo(resource.getIdElement().getIdPart()); + assertThat(resource1).isNotSameAs(resource); + + } + + @Test + public void testContainedResourcesNotAutoContainedWhenConfiguredNotToDoSo() { + MedicationDispense md = new MedicationDispense(); + md.addIdentifier().setValue("DISPENSE"); + + Medication med = new Medication(); + med.getCode().setText("MED"); + md.setMedication(new Reference(med)); + + ourCtx.getParserOptions().setAutoContainReferenceTargetsWithNoId(false); + String encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(md); + assertEquals("{\"resourceType\":\"MedicationDispense\",\"identifier\":[{\"value\":\"DISPENSE\"}],\"medicationReference\":{}}", encoded); + + ourCtx.getParserOptions().setAutoContainReferenceTargetsWithNoId(true); + encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(md); + String containedResourceId = med.getId(); + String containedResourceReferenceId = "#" + containedResourceId; + assertThat(encoded).contains("{\"resourceType\":\"MedicationDispense\",\"contained\":[{\"resourceType\":\"Medication\",\"id\":\"" + containedResourceId + "\",\"code\":{\"text\":\"MED\"}}],\"identifier\":[{\"value\":\"DISPENSE\"}],\"medicationReference\":{\"reference\":\"" + containedResourceReferenceId +"\"}}"); //Note we dont check exact ID since its a GUID + } + + @Test + public void testParseBundleWithMultipleNestedContainedResources() throws Exception { + String text = loadResource("/bundle-with-two-patient-resources.json"); + + Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, text); + assertEquals(Boolean.TRUE, bundle.getUserData(BaseParser.RESOURCE_CREATED_BY_PARSER)); + assertEquals(Boolean.TRUE, bundle.getEntry().get(0).getResource().getUserData(BaseParser.RESOURCE_CREATED_BY_PARSER)); + assertEquals(Boolean.TRUE, bundle.getEntry().get(1).getResource().getUserData(BaseParser.RESOURCE_CREATED_BY_PARSER)); + + assertEquals("12346", getPatientIdValue(bundle, 0)); + assertEquals("12345", getPatientIdValue(bundle, 1)); + } + + private String getPatientIdValue(Bundle input, int entry) { + final DocumentReference documentReference = (DocumentReference) input.getEntry().get(entry).getResource(); + final Patient patient = (Patient) documentReference.getSubject().getResource(); + return patient.getIdentifier().get(0).getValue(); + } + /** + * See #814 + */ + @Test + public void testDuplicateContainedResourcesNotOutputtedTwiceWithManualIds() { + MedicationDispense md = new MedicationDispense(); + + MedicationRequest mr = new MedicationRequest(); + mr.setId("MR"); + md.addAuthorizingPrescription().setResource(mr); + + Medication med = new Medication(); + med.setId("MED"); + md.setMedication(new Reference(med)); + mr.setMedication(new Reference(med)); + + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(md); + ourLog.info(encoded); + + int idx = encoded.indexOf("\"Medication\""); + assertThat(idx).isNotEqualTo(-1); + + idx = encoded.indexOf("\"Medication\"", idx + 1); + assertEquals(-1, idx); + + } + /* + * See #814 + */ + @Test + public void testDuplicateContainedResourcesNotOutputtedTwiceWithManualIdsAndManualAddition() { + MedicationDispense md = new MedicationDispense(); + + MedicationRequest mr = new MedicationRequest(); + mr.setId("#MR"); + md.addAuthorizingPrescription().setResource(mr); + + Medication med = new Medication(); + med.setId("#MED"); + + Reference medRef = new Reference(); + medRef.setReference("#MED"); + md.setMedication(medRef); + mr.setMedication(medRef); + + md.getContained().add(mr); + md.getContained().add(med); + + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(md); + ourLog.info(encoded); + + int idx = encoded.indexOf("\"Medication\""); + assertThat(idx).isNotEqualTo(-1); + + idx = encoded.indexOf("\"Medication\"", idx + 1); + assertEquals(-1, idx); + + } + + @Test + public void testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocalFirst() { + + Observation obs = new Observation(); + + Patient pt = new Patient(); + pt.setId("1"); + pt.addName().setFamily("FAM"); + obs.getSubject().setReference("#1"); + obs.getContained().add(pt); + + Encounter enc = new Encounter(); + enc.setStatus(Encounter.EncounterStatus.ARRIVED); + obs.getEncounter().setResource(enc); + + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs); + ourLog.info(encoded); + + obs = ourCtx.newJsonParser().parseResource(Observation.class, encoded); + assertEquals("1", obs.getContained().get(0).getId()); + assertEquals(enc.getId(), obs.getContained().get(1).getId()); + + pt = (Patient) obs.getSubject().getResource(); + assertEquals("FAM", pt.getNameFirstRep().getFamily()); + + enc = (Encounter) obs.getEncounter().getResource(); + assertEquals(Encounter.EncounterStatus.ARRIVED, enc.getStatus()); + } + + @Test + public void testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocalLast() { + + Observation obs = new Observation(); + + Patient pt = new Patient(); + pt.addName().setFamily("FAM"); + obs.getSubject().setResource(pt); + + Encounter enc = new Encounter(); + enc.setId("1"); + enc.setStatus(Encounter.EncounterStatus.ARRIVED); + obs.getEncounter().setReference("#1"); + obs.getContained().add(enc); + + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs); + ourLog.info(encoded); + + obs = ourCtx.newJsonParser().parseResource(Observation.class, encoded); + assertEquals("1", obs.getContained().get(0).getId()); + assertEquals(pt.getId(), obs.getContained().get(1).getId()); + + pt = (Patient) obs.getSubject().getResource(); + assertEquals("FAM", pt.getNameFirstRep().getFamily()); + + enc = (Encounter) obs.getEncounter().getResource(); + assertEquals(Encounter.EncounterStatus.ARRIVED, enc.getStatus()); + } + + /** + * Ensure that a contained bundle doesn't cause a crash + */ + @Test + public void testEncodeContainedBundle() { + String auditEvent = """ + { + "resourceType": "AuditEvent", + "contained": [ { + "resourceType": "Bundle", + "id": "REASONS", + "entry": [ { + "resource": { + "resourceType": "Condition", + "id": "123" + } + } ] + }, { + "resourceType": "MeasureReport", + "id": "MRPT5000602611RD", + "evaluatedResource": [ { + "reference": "#REASONS" + } ] + } ], + "entity": [ { + "what": { + "reference": "#MRPT5000602611RD" + } + } ] + }"""; + AuditEvent ae = ourCtx.newJsonParser().parseResource(AuditEvent.class, auditEvent); + String auditEventAsString = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(ae); + assertEquals(auditEvent, auditEventAsString); + } + + + /** + * Ensure that a contained bundle doesn't cause a crash + */ + @Test + public void testParseAndEncodePreservesContainedResourceOrder() { + String auditEvent = """ + { + "resourceType": "AuditEvent", + "contained": [ { + "resourceType": "Observation", + "id": "A", + "identifier": [ { + "value": "A" + } ] + }, { + "resourceType": "Observation", + "id": "B", + "identifier": [ { + "value": "B" + } ] + } ], + "entity": [ { + "what": { + "reference": "#B" + } + }, { + "what": { + "reference": "#A" + } + } ] + }"""; + + ourLog.info("Input: {}", auditEvent); + AuditEvent ae = ourCtx.newJsonParser().parseResource(AuditEvent.class, auditEvent); + assertEquals("A", ae.getContained().get(0).getId()); + assertEquals("B", ae.getContained().get(1).getId()); + assertEquals("#B", ae.getEntity().get(0).getWhat().getReference()); + assertEquals("#A", ae.getEntity().get(1).getWhat().getReference()); + + String serialized = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(ae); + assertEquals(auditEvent, serialized); + + } + + @Test + public void testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocalLast2() { + + MedicationRequest mr = new MedicationRequest(); + Practitioner pract = new Practitioner().setActive(true); + mr.getRequester().setResource(pract); + + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(mr); + ourLog.info(encoded); + mr = ourCtx.newJsonParser().parseResource(MedicationRequest.class, encoded); + + Medication med = new Medication().setStatus(Medication.MedicationStatus.ACTIVE); + mr.setMedication(new Reference(med)); + encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(mr); + ourLog.info(encoded); + mr = ourCtx.newJsonParser().parseResource(MedicationRequest.class, encoded); + + assertEquals(pract.getId(), mr.getContained().get(0).getId()); + assertEquals(med.getId(), mr.getContained().get(1).getId()); + + } + + @Test + public void testContainedResourceIdIsReadWithoutAddingHash() throws IOException { + String text = loadResource("/observation-with-contained-specimen.json"); + + Observation observation = ourCtx.newJsonParser().parseResource(Observation.class, text); + assertThat(observation.getSpecimen().getReference()).isEqualTo("#contained-id"); + //TODO the parsed resource has an added `#` in the contained resource id. + assertSame(observation.getContained().get(0), observation.getSpecimen().getResource()); + assertThat(observation.getContained().get(0).getId()).isEqualTo("contained-id"); + } + + @Test + public void testReferenceCreatedByStringDoesntMutateContained() { + Observation observation = new Observation(); + observation.setId("123"); + Specimen specimen = new Specimen(); + specimen.setId("contained-id"); + observation.setSpecimen(new Reference("#contained-id")); + + observation.getContained().add(specimen); + + String text = ourCtx.newJsonParser().encodeResourceToString(observation); + + assertThat(text).contains("\"reference\":\"#contained-id\""); + assertThat(text).contains("\"id\":\"contained-id\""); + + assertThat(observation.getContained().size()).isEqualTo(1); + /*TODO despite correctly encoding the contained resource, the original contained resource is mutated and given + an id prefixed with a hash. + */ + assertThat(observation.getContained().get(0).getId()).isEqualTo("contained-id"); + } + + @Test + public void testReferenceCreatedByResourceDoesntMutateContained() { + + Specimen specimen = new Specimen(); + specimen.setId("contained-id"); + + Observation observation = new Observation(); + observation.setId("123"); + observation.setSpecimen(new Reference(specimen)); + + observation.getContained().add(specimen); + + String text = ourCtx.newJsonParser().encodeResourceToString(observation); + + assertThat(text).contains("\"reference\":\"#contained-id\""); + assertThat(text).contains("\"id\":\"contained-id\""); + + assertThat(observation.getContained().size()).isEqualTo(1); + + assertThat(observation.getContained().get(0).getId()).isEqualTo("contained-id"); + } + + @Test + public void testReferenceCreatedByResourceDoesntMutateEmptyContained() { + Specimen specimen = new Specimen(); + specimen.setReceivedTimeElement(new DateTimeType("2011-01-01")); + + Observation observation = new Observation(); + observation.setId("O1"); + observation.setStatus(Observation.ObservationStatus.FINAL); + observation.setSpecimen(new Reference(specimen)); + + String text = ourCtx.newJsonParser().encodeResourceToString(observation); + + // When encoding, if the reference does not have an id, it is generated in the FhirTerser + String specimenReferenceId = observation.getSpecimen().getResource().getIdElement().getValue(); + assertThat(specimenReferenceId).doesNotStartWith("#"); + assertSame(observation.getSpecimen().getResource(), specimen); + + // the terser doesn't add a new contained element to the observation + assertThat(observation.getContained()).isEmpty(); + + // the terser doesn't mutate the id of the contained resource either + assertThat(specimen.getId()).doesNotStartWith("#"); + + // However the encoded text contains both a single contained resource, as well as the reference to it. + assertThat(text).contains("\"reference\":\"#"+specimenReferenceId+"\""); + assertThat(text).contains("\"id\":\""+specimenReferenceId+"\""); + + } + + + @Test + public void testReferenceCreatedByIdTypeDoesntMutateContained() { + Specimen specimen = new Specimen(); + specimen.setId("contained-id"); + specimen.setReceivedTimeElement(new DateTimeType("2011-01-01")); + + Observation observation = new Observation(); + observation.setId("O1"); + observation.setStatus(Observation.ObservationStatus.FINAL); + observation.setSpecimen(new Reference(new IdType("#contained-id"))); + + observation.getContained().add(specimen); + + String text = ourCtx.newJsonParser().encodeResourceToString(observation); + + // When encoding, + String specimenReferenceId = observation.getSpecimen().getReference(); + assertThat(specimenReferenceId).startsWith("#"); + + // TODO why does the terser mutate the contained element of the original resource? + String specimenContainedId = observation.getContained().get(0).getIdElement().getValue(); + assertThat(specimenContainedId).isEqualTo("contained-id"); + + // However the encoded text contains both a single contained resource, as well as the reference to it. + assertThat(text).contains("\"reference\":\""+specimenReferenceId+"\""); + assertThat(text).contains("\"id\":\""+specimenReferenceId.substring(1)+"\""); + + } + } + + @DatatypeDef( name = "UnknownPrimitiveType" ) diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java index a310005e56d1..a5416106b4ea 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java @@ -114,8 +114,8 @@ public void testParseAndEncodePreservesContainedResourceOrder() { ourLog.info("Input: {}", auditEvent); AuditEvent ae = ourCtx.newXmlParser().parseResource(AuditEvent.class, auditEvent); - assertEquals("#A", ae.getContained().get(0).getId()); - assertEquals("#B", ae.getContained().get(1).getId()); + assertEquals("A", ae.getContained().get(0).getId()); + assertEquals("B", ae.getContained().get(1).getId()); assertEquals("#B", ae.getEntity().get(0).getWhat().getReference()); assertEquals("#A", ae.getEntity().get(1).getWhat().getReference()); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/FhirTerserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/FhirTerserR4Test.java index 0f2e122fd6f9..bf0973bdc932 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/FhirTerserR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/FhirTerserR4Test.java @@ -18,6 +18,7 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.BooleanType; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.DiagnosticReport; import org.hl7.fhir.r4.model.DocumentReference; import org.hl7.fhir.r4.model.Enumeration; import org.hl7.fhir.r4.model.Enumerations; @@ -42,6 +43,7 @@ import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.ResourceType; import org.hl7.fhir.r4.model.SimpleQuantity; +import org.hl7.fhir.r4.model.Specimen; import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.Substance; import org.hl7.fhir.r4.model.ValueSet; @@ -68,6 +70,7 @@ import java.util.stream.Collectors; import static ca.uhn.fhir.test.utilities.UuidUtils.HASH_UUID_PATTERN; +import static ca.uhn.fhir.test.utilities.UuidUtils.UUID_PATTERN; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -194,12 +197,12 @@ public void testContainResourcesWithModify() { FhirTerser.ContainedResources contained = myCtx.newTerser().containResources(mr, FhirTerser.OptionsEnum.MODIFY_RESOURCE, FhirTerser.OptionsEnum.STORE_AND_REUSE_RESULTS); - assertThat(mr.getContained().get(0).getId()).containsPattern(HASH_UUID_PATTERN); - assertThat(mr.getContained().get(1).getId()).containsPattern(HASH_UUID_PATTERN); + assertThat(mr.getContained().get(0).getId()).containsPattern(UUID_PATTERN); + assertThat(mr.getContained().get(1).getId()).containsPattern(UUID_PATTERN); assertEquals(ResourceType.Medication, mr.getContained().get(0).getResourceType()); assertEquals(ResourceType.Practitioner, mr.getContained().get(1).getResourceType()); - assertEquals(mr.getContained().get(0).getId(), mr.getMedicationReference().getReference()); - assertEquals(mr.getContained().get(1).getId(), mr.getRequester().getReference()); + assertEquals("#" + mr.getContained().get(0).getId(), mr.getMedicationReference().getReference()); + assertEquals("#" + mr.getContained().get(1).getId(), mr.getRequester().getReference()); FhirTerser.ContainedResources secondPass = myCtx.newTerser().containResources(mr, FhirTerser.OptionsEnum.MODIFY_RESOURCE, FhirTerser.OptionsEnum.STORE_AND_REUSE_RESULTS); assertThat(secondPass).isSameAs(contained); @@ -218,12 +221,12 @@ public void testContainedResourcesWithModify_DoubleLink() { myCtx.newTerser().containResources(medAdmin, FhirTerser.OptionsEnum.MODIFY_RESOURCE, FhirTerser.OptionsEnum.STORE_AND_REUSE_RESULTS); - assertThat(medAdmin.getContained().get(0).getId()).containsPattern(HASH_UUID_PATTERN); - assertThat(medAdmin.getContained().get(1).getId()).containsPattern(HASH_UUID_PATTERN); + assertThat(medAdmin.getContained().get(0).getId()).containsPattern(UUID_PATTERN); + assertThat(medAdmin.getContained().get(1).getId()).containsPattern(UUID_PATTERN); assertEquals(ResourceType.Medication, medAdmin.getContained().get(0).getResourceType()); assertEquals(ResourceType.Substance, medAdmin.getContained().get(1).getResourceType()); - assertEquals(medAdmin.getContained().get(0).getId(), medAdmin.getMedicationReference().getReference()); - assertEquals(medAdmin.getContained().get(1).getId(), ((Medication) (medAdmin.getContained().get(0))).getIngredientFirstRep().getItemReference().getReference()); + assertEquals("#" + medAdmin.getContained().get(0).getId(), medAdmin.getMedicationReference().getReference()); + assertEquals("#" + medAdmin.getContained().get(1).getId(), ((Medication) (medAdmin.getContained().get(0))).getIngredientFirstRep().getItemReference().getReference()); } diff --git a/hapi-fhir-structures-r4/src/test/resources/observation-with-contained-specimen-hash-id.json b/hapi-fhir-structures-r4/src/test/resources/observation-with-contained-specimen-hash-id.json new file mode 100644 index 000000000000..350ef5606790 --- /dev/null +++ b/hapi-fhir-structures-r4/src/test/resources/observation-with-contained-specimen-hash-id.json @@ -0,0 +1,14 @@ +{ + "resourceType": "Observation", + "id": "O1", + "contained": [ + { + "resourceType": "Specimen", + "id": "#contained-id" + } + ], + "status": "final", + "specimen": { + "reference": "#contained-id" + } +} diff --git a/hapi-fhir-structures-r4/src/test/resources/observation-with-contained-specimen.json b/hapi-fhir-structures-r4/src/test/resources/observation-with-contained-specimen.json new file mode 100644 index 000000000000..49a8153cfc71 --- /dev/null +++ b/hapi-fhir-structures-r4/src/test/resources/observation-with-contained-specimen.json @@ -0,0 +1,14 @@ +{ + "resourceType": "Observation", + "id": "O1", + "contained": [ + { + "resourceType": "Specimen", + "id": "contained-id" + } + ], + "status": "final", + "specimen": { + "reference": "#contained-id" + } +} diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirDefaultPolicyAdvisor.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirDefaultPolicyAdvisor.java index 83e97cf95724..7ca6ff67e6c0 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirDefaultPolicyAdvisor.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirDefaultPolicyAdvisor.java @@ -11,6 +11,7 @@ import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; import org.hl7.fhir.utilities.validation.ValidationMessage; +import org.hl7.fhir.validation.instance.advisor.BasePolicyAdvisorForFullValidation; import java.util.Arrays; import java.util.EnumSet; @@ -95,4 +96,14 @@ public boolean isSuppressMessageId(String path, String messageId) { public ReferenceValidationPolicy getReferencePolicy() { return ReferenceValidationPolicy.IGNORE; } + + @Override + public IValidationPolicyAdvisor getPolicyAdvisor() { + return new BasePolicyAdvisorForFullValidation(getReferencePolicy()); + } + + @Override + public IValidationPolicyAdvisor setPolicyAdvisor(IValidationPolicyAdvisor policyAdvisor) { + return null; + } } diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapper.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapper.java index 0121cbfbb19a..d43583f28e8b 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapper.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapper.java @@ -20,6 +20,7 @@ import org.hl7.fhir.r5.utils.XVerExtensionManager; import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; +import org.hl7.fhir.r5.utils.validation.ValidatorSession; import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel; import org.hl7.fhir.r5.utils.validation.constants.IdStatus; import org.hl7.fhir.utilities.i18n.I18nConstants; @@ -120,7 +121,7 @@ public List validate( FHIRPathEngine.IEvaluationContext evaluationCtx = new FhirInstanceValidator.NullEvaluationContext(); XVerExtensionManager xverManager = new XVerExtensionManager(theWorkerContext); try { - v = new InstanceValidator(theWorkerContext, evaluationCtx, xverManager); + v = new InstanceValidator(theWorkerContext, evaluationCtx, xverManager, new ValidatorSession()); } catch (Exception e) { throw new ConfigurationException(Msg.code(648) + e.getMessage(), e); } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java index 775bf7efff4d..16e70d10f95b 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java @@ -513,9 +513,6 @@ else if (t.getMessage().contains("bdl-7") && t.getMessage().contains("Error eval else if (t.getMessage().contains("side is inherently a collection") && t.getMessage().endsWith("may fail or return false if there is more than one item in the content being evaluated")) { // Some DSTU3 FHIRPath expressions now produce warnings if a singleton is compared to a collection that potentially has > 1 elements return false; - } else if (t.getMessage().contains("When HL7 is publishing a resource, the owning committee must be stated using the http://hl7.org/fhir/StructureDefinition/structuredefinition-wg extension")) { - // DSTU3 resources predate this strict requirement - return false; } else if (t.getMessage().equals("The nominated WG 'rcrim' is unknown")) { //The rcrim workgroup is now brr http://www.hl7.org/Special/committees/rcrim/index.cfm return false; diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/utils/FhirPathEngineR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/utils/FhirPathEngineR4Test.java index 1c2382b6aac1..40dc195cda47 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/utils/FhirPathEngineR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/utils/FhirPathEngineR4Test.java @@ -2,6 +2,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.fhirpath.BaseValidationTestWithInlineMocks; +import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.dstu3.utils.FhirPathEngineTest; @@ -24,6 +25,8 @@ import org.hl7.fhir.r4.model.StructureDefinition; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import java.util.List; @@ -31,6 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; public class FhirPathEngineR4Test extends BaseValidationTestWithInlineMocks { @@ -39,32 +43,100 @@ public class FhirPathEngineR4Test extends BaseValidationTestWithInlineMocks { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirPathEngineTest.class); private static FHIRPathEngine ourEngine; - @Test - public void testCrossResourceBoundaries() throws FHIRException { - Specimen specimen = new Specimen(); - specimen.setReceivedTimeElement(new DateTimeType("2011-01-01")); - - Observation o = new Observation(); - o.setId("O1"); - o.setStatus(Observation.ObservationStatus.FINAL); - o.setSpecimen(new Reference(specimen)); - IParser p = ourCtx.newJsonParser(); - o = (Observation) p.parseResource(p.encodeResourceToString(o)); - - List value; + @Nested + @DisplayName("FHIRPath access for contained resources.") + class CrossResourceBoundaries{ + + /** + * When a contained resource without an ID is set as a reference directly, the JSON parser will automatically + * generate a valid ID for the contained resource, add it to the and the . + */ + @Test + public void testParserGeneratedContainedId() throws FHIRException { + Specimen specimen = new Specimen(); + specimen.setReceivedTimeElement(new DateTimeType("2011-01-01")); + + Observation o = new Observation(); + o.setId("O1"); + o.setStatus(Observation.ObservationStatus.FINAL); + o.setSpecimen(new Reference(specimen)); + + IParser p = ourCtx.newJsonParser(); + o = (Observation) p.parseResource(p.encodeResourceToString(o)); + + List value; + + value = ourCtx.newFhirPath().evaluate(o, "Observation.specimen", Base.class); + assertThat(value).hasSize(1); + assertSpecimenFoundAndDateCorrect(o); + } + + /** If a contained resource with an ID exists in the contained field, it can be referred to by a # prefixed + * reference. + *

+ * This is the most basic FHIR structure that allow FHIRPath to resolve references to contained resources. + **/ + @Test + public void testManuallySetContainedAndReference() throws FHIRException { + Specimen specimen = new Specimen(); + specimen.setId("S1"); + specimen.setReceivedTimeElement(new DateTimeType("2011-01-01")); + + Observation o = new Observation(); + o.setId("O1"); + o.setStatus(Observation.ObservationStatus.FINAL); + o.setSpecimen(new Reference("#S1")); + + o.getContained().add(specimen); + + List value; + + value = ourCtx.newFhirPath().evaluate(o, "Observation.specimen", Base.class); + assertThat(value).hasSize(1); + + assertSpecimenFoundAndDateCorrect(o); + } + + /**If a contained resource with an ID exists in the contained field, it can be referred to by a # prefixed + * reference, as well as included as the resource for that reference + **/ + @Test + public void testManuallySetContainedAndReferenceWithResource() throws FHIRException { + Specimen specimen = new Specimen(); + specimen.setId("S1"); + specimen.setReceivedTimeElement(new DateTimeType("2011-01-01")); + + Observation o = new Observation(); + o.setId("O1"); + o.setStatus(Observation.ObservationStatus.FINAL); + Reference reference = new Reference("#S1"); + reference.setResource(specimen); + o.setSpecimen(reference); + + o.getContained().add(specimen); + + List value; + + value = ourCtx.newFhirPath().evaluate(o, "Observation.specimen", Base.class); + assertThat(value).hasSize(1); + assertSpecimenFoundAndDateCorrect(o); + } + + private void assertSpecimenFoundAndDateCorrect(Observation o) { + List value; + value = ourCtx.newFhirPath().evaluate(o, "Observation.specimen.resolve()", Base.class); + assertThat(value).hasSize(1); + + value = ourCtx.newFhirPath().evaluate(o, "Observation.specimen.resolve().receivedTime", Base.class); + assertThat(value).hasSize(1); + assertEquals("2011-01-01", ((DateTimeType) value.get(0)).getValueAsString()); + } + } - value = ourCtx.newFhirPath().evaluate(o, "Observation.specimen", Base.class); - assertThat(value).hasSize(1); - value = ourCtx.newFhirPath().evaluate(o, "Observation.specimen.resolve()", Base.class); - assertThat(value).hasSize(1); - value = ourCtx.newFhirPath().evaluate(o, "Observation.specimen.resolve().receivedTime", Base.class); - assertThat(value).hasSize(1); - assertEquals("2011-01-01", ((DateTimeType) value.get(0)).getValueAsString()); - } diff --git a/pom.xml b/pom.xml index 03e8eac5cf99..7002ac0f3415 100644 --- a/pom.xml +++ b/pom.xml @@ -994,7 +994,7 @@ - 6.4.0 + 6.4.4 2.41.1 -Dfile.encoding=UTF-8 -Xmx2048m 3.5.2