Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix fhirpath expression evaluation with _fhirpath parameter when resolve() references resources within a Bundle #5565

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Fix fhirpath expression evaluation with _fhirpath parameter when reso…
…lve() references resources within a Bundle
  • Loading branch information
Martha Mitran committed Dec 21, 2023
commit e95dc0c1597ccf5012862f18d04cad2f48a8fdd2
44 changes: 44 additions & 0 deletions hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.PatchTypeEnum;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
Expand All @@ -36,6 +37,7 @@
import ca.uhn.fhir.util.bundle.SearchBundleEntryParts;
import com.google.common.collect.Sets;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBinary;
Expand Down Expand Up @@ -642,6 +644,48 @@ public static <T extends IBaseResource> List<T> toListOfResourcesOfType(
return retVal;
}

public static IBase getReferenceInBundle(
@Nonnull FhirContext theFhirContext, @Nonnull String theUrl, @Nullable IBase theAppContext) {
/*
* If this is a reference that is a UUID, we must be looking for local
* references within a Bundle
*/
if (theAppContext instanceof IBaseBundle && isNotBlank(theUrl) && !theUrl.startsWith("#")) {
codeforgreen marked this conversation as resolved.
Show resolved Hide resolved
String unqualifiedVersionlessReference;
boolean isPlaceholderReference;
if (theUrl.startsWith("urn:")) {
isPlaceholderReference = true;
unqualifiedVersionlessReference = null;
} else {
isPlaceholderReference = false;
unqualifiedVersionlessReference =
new IdDt(theUrl).toUnqualifiedVersionless().getValue();
}

List<BundleEntryParts> entries = BundleUtil.toListOfEntries(theFhirContext, (IBaseBundle) theAppContext);
for (BundleEntryParts next : entries) {
IBaseResource nextResource = next.getResource();
if (nextResource == null) {
continue;
}
if (isPlaceholderReference) {
if (theUrl.equals(next.getUrl())
|| theUrl.equals(nextResource.getIdElement().getValue())) {
return nextResource;
}
} else {
if (unqualifiedVersionlessReference.equals(nextResource
.getIdElement()
.toUnqualifiedVersionless()
.getValue())) {
return nextResource;
}
}
}
}
return null;
}

/**
* DSTU3 did not allow the PATCH verb for transaction bundles- so instead we infer that a bundle
* is a patch if the payload is a binary resource containing a patch. This method
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
type: fix
issue: 5564
title: "Previously, FHIRPath expression evaluation when using the `_fhirpath` parameter would not work on chained
use of 'resolve()'. This was most notable when using `_fhirpath` with FHIR Documents (i.e. 'Bundle' of type 'document'
where 'entry[0]' is a 'Composition'). This has now been fixed."
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,10 @@
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.HapiExtensions;
import ca.uhn.fhir.util.StringUtil;
import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.util.bundle.BundleEntryParts;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Sets;
import jakarta.annotation.Nonnull;
Expand All @@ -59,14 +57,12 @@
import org.fhir.ucum.Pair;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseEnumeration;
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.IdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

Expand Down Expand Up @@ -2004,47 +2000,6 @@ public void start() {
}
}

@SuppressWarnings("unchecked")
protected final <T extends IBase> T resolveResourceInBundleWithPlaceholderId(Object theAppContext, String theUrl) {
/*
* If this is a reference that is a UUID, we must be looking for local
* references within a Bundle
*/
if (theAppContext instanceof IBaseBundle && isNotBlank(theUrl) && !theUrl.startsWith("#")) {
String unqualifiedVersionlessReference;
boolean isPlaceholderReference;
if (theUrl.startsWith("urn:")) {
isPlaceholderReference = true;
unqualifiedVersionlessReference = null;
} else {
isPlaceholderReference = false;
unqualifiedVersionlessReference =
new IdType(theUrl).toUnqualifiedVersionless().getValue();
}

List<BundleEntryParts> entries = BundleUtil.toListOfEntries(getContext(), (IBaseBundle) theAppContext);
for (BundleEntryParts next : entries) {
if (next.getResource() != null) {
if (isPlaceholderReference) {
if (theUrl.equals(next.getUrl())
|| theUrl.equals(
next.getResource().getIdElement().getValue())) {
return (T) next.getResource();
}
} else {
if (unqualifiedVersionlessReference.equals(next.getResource()
.getIdElement()
.toUnqualifiedVersionless()
.getValue())) {
return (T) next.getResource();
}
}
}
}
}
return null;
}

@FunctionalInterface
public interface IValueExtractor {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import ca.uhn.fhir.sl.cache.Cache;
import ca.uhn.fhir.sl.cache.CacheFactory;
import ca.uhn.fhir.util.BundleUtil;
import com.google.common.annotations.VisibleForTesting;
import jakarta.annotation.PostConstruct;
import org.hl7.fhir.exceptions.FHIRException;
Expand Down Expand Up @@ -139,7 +140,7 @@ public List<Base> executeFunction(

@Override
public Base resolveReference(Object theAppContext, String theUrl, Base theRefContext) throws FHIRException {
Base retVal = resolveResourceInBundleWithPlaceholderId(theAppContext, theUrl);
Base retVal = (Base) BundleUtil.getReferenceInBundle(getContext(), theUrl, theRefContext);
if (retVal != null) {
return retVal;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import ca.uhn.fhir.sl.cache.Cache;
import ca.uhn.fhir.sl.cache.CacheFactory;
import ca.uhn.fhir.util.BundleUtil;
import com.google.common.annotations.VisibleForTesting;
import jakarta.annotation.PostConstruct;
import org.hl7.fhir.exceptions.FHIRException;
Expand Down Expand Up @@ -139,7 +140,7 @@ public List<Base> executeFunction(

@Override
public Base resolveReference(Object theAppContext, String theUrl, Base refContext) throws FHIRException {
Base retVal = resolveResourceInBundleWithPlaceholderId(theAppContext, theUrl);
Base retVal = (Base) BundleUtil.getReferenceInBundle(getContext(), theUrl, refContext);
if (retVal != null) {
return retVal;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import ca.uhn.fhir.sl.cache.Cache;
import ca.uhn.fhir.sl.cache.CacheFactory;
import ca.uhn.fhir.util.BundleUtil;
import jakarta.annotation.PostConstruct;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
Expand Down Expand Up @@ -136,7 +137,7 @@ public List<Base> executeFunction(

@Override
public Base resolveReference(Object appContext, String theUrl, Base refContext) throws FHIRException {
Base retVal = resolveResourceInBundleWithPlaceholderId(appContext, theUrl);
Base retVal = (Base) BundleUtil.getReferenceInBundle(getContext(), theUrl, refContext);
if (retVal != null) {
return retVal;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,22 @@
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.fhirpath.FhirPathExecutionException;
import ca.uhn.fhir.fhirpath.IFhirPath;
import ca.uhn.fhir.fhirpath.IFhirPathEvaluationContext;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.ResponseDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.ParametersUtil;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;

import java.util.List;

Expand Down Expand Up @@ -65,6 +70,12 @@ public void preProcessOutgoingResponse(RequestDetails theRequestDetails, Respons
ParametersUtil.addPartString(ctx, resultPart, "expression", expression);

IFhirPath fhirPath = ctx.newFhirPath();
fhirPath.setEvaluationContext(new IFhirPathEvaluationContext() {
@Override
public IBase resolveReference(@Nonnull IIdType theReference, @Nullable IBase theContext) {
return BundleUtil.getReferenceInBundle(ctx, theReference.getValue(), responseResource);
}
});
List<IBase> outputs;
try {
outputs = fhirPath.evaluate(responseResource, expression, IBase.class);
Expand Down
Loading
Loading