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 4e4319076c3..294761ae174 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 @@ -287,9 +287,33 @@ public class FhirTerser { return retVal; } + /** + * Extracts all outbound references from a resource + * + * @param theResource the resource to be analyzed + * @return a list of references to other resources + */ public List getAllResourceReferences(final IBaseResource theResource) { + return getAllResourceReferencesExcluding(theResource, Lists.newArrayList()); + } + + /** + * Extracts all outbound references from a resource, excluding any that are located on black-listed parts of the + * resource + * + * @param theResource the resource to be analyzed + * @param thePathsToExclude a list of dot-delimited paths not to include in the result + * @return a list of references to other resources + */ + public List getAllResourceReferencesExcluding( + final IBaseResource theResource, List thePathsToExclude) { final ArrayList retVal = new ArrayList<>(); BaseRuntimeElementCompositeDefinition def = myContext.getResourceDefinition(theResource); + List> tokenizedPathsToExclude = thePathsToExclude.stream() + .map(path -> StringUtils.split(path, ".")) + .map(Lists::newArrayList) + .collect(Collectors.toList()); + visit(newMap(), theResource, theResource, null, null, def, new IModelVisitor() { @Override public void acceptElement( @@ -301,6 +325,10 @@ public class FhirTerser { if (theElement == null || theElement.isEmpty()) { return; } + + if (thePathToElement != null && pathShouldBeExcluded(tokenizedPathsToExclude, thePathToElement)) { + return; + } if (IBaseReference.class.isAssignableFrom(theElement.getClass())) { retVal.add(new ResourceReferenceInfo( myContext, theOuterResource, thePathToElement, (IBaseReference) theElement)); @@ -310,6 +338,19 @@ public class FhirTerser { return retVal; } + private boolean pathShouldBeExcluded(List> theTokenizedPathsToExclude, List thePathToElement) { + return theTokenizedPathsToExclude.stream().anyMatch(p -> { + // Check whether the path to the element starts with the path to be excluded + if (p.size() > thePathToElement.size()) { + return false; + } + + List prefix = thePathToElement.subList(0, p.size()); + + return Objects.equals(p, prefix); + }); + } + private BaseRuntimeChildDefinition getDefinition( BaseRuntimeElementCompositeDefinition theCurrentDef, List theSubList) { BaseRuntimeChildDefinition nextDef = theCurrentDef.getChildByNameOrThrowDataFormatException(theSubList.get(0)); 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 3aba95d075a..5182d2491a9 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 @@ -12,6 +12,7 @@ import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseExtension; import org.hl7.fhir.instance.model.api.IBaseReference; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.BooleanType; import org.hl7.fhir.r4.model.Bundle; @@ -32,6 +33,7 @@ import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Patient.LinkType; import org.hl7.fhir.r4.model.Practitioner; import org.hl7.fhir.r4.model.PrimitiveType; +import org.hl7.fhir.r4.model.Provenance; import org.hl7.fhir.r4.model.Quantity; import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.ResourceType; @@ -511,6 +513,54 @@ public class FhirTerserR4Test { } + @Test + public void testGetAllResourceReferences() { + // setup + Provenance p = new Provenance(); + p.addTarget(new Reference("Observation/1")); + p.addTarget(new Reference("Observation/2")); + p.setLocation(new Reference("Location/3")); + p.getAgentFirstRep().setWho(new Reference("Practitioner/4")); + p.getAgentFirstRep().setOnBehalfOf(new Reference("Organization/5")); + p.getEntityFirstRep().setWhat(new Reference("DocumentReference/6")); + + // execute + FhirTerser t = myCtx.newTerser(); + List references = t.getAllResourceReferences(p); + + // validate + assertEquals(6, references.size()); + assertThat(toResourceIds(references), containsInAnyOrder("Observation/1", "Observation/2", "Location/3", "Practitioner/4", "Organization/5", "DocumentReference/6")); + } + + @Test + public void testGetAllResourceReferencesExcluding() { + // setup + Provenance p = new Provenance(); + p.addTarget(new Reference("Observation/1")); + p.addTarget(new Reference("Observation/2")); + p.setLocation(new Reference("Location/3")); + p.getAgentFirstRep().setWho(new Reference("Practitioner/4")); + p.getAgentFirstRep().setOnBehalfOf(new Reference("Organization/5")); + p.getEntityFirstRep().setWhat(new Reference("DocumentReference/6")); + + // execute + FhirTerser t = myCtx.newTerser(); + List references = t.getAllResourceReferencesExcluding(p, List.of("target")); + + // validate + assertEquals(4, references.size()); + assertThat(toResourceIds(references), containsInAnyOrder("Location/3", "Practitioner/4", "Organization/5", "DocumentReference/6")); + } + + private List toResourceIds(List references) { + return references.stream() + .map(ResourceReferenceInfo::getResourceReference) + .map(IBaseReference::getReferenceElement) + .map(IIdType::getValue) + .collect(Collectors.toList()); + } + @Test public void testGetResourceReferenceInExtension() { Patient p = new Patient();