theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath, boolean theWantLocalReferences) {
if (theValue instanceof IBaseResource) {
+ myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, (IBaseResource) theValue);
+ theParams.add(myPathAndRef);
return;
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/IResourceLinkResolver.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/IResourceLinkResolver.java
index 31e4f746b7d..7628770eabc 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/IResourceLinkResolver.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/IResourceLinkResolver.java
@@ -27,6 +27,7 @@ import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import org.hl7.fhir.instance.model.api.IBaseResource;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
public interface IResourceLinkResolver {
@@ -35,16 +36,34 @@ public interface IResourceLinkResolver {
* so that we can create indexed links between resources, and so that we can validate that the target actually
* exists in cases where we need to check that.
*
- * This method returns an {@link IResourceLookup} so as to avoid needing to resolve the entire resource.
+ * This method returns an {@link IResourceLookup} to avoid needing to resolve the entire resource.
*
* @param theRequestPartitionId The partition ID of the target resource
- * @param theSourceResourceName
+ * @param theSourceResourceName The resource type for the resource containing the reference
* @param thePathAndRef The path and reference
* @param theRequest The incoming request, if any
- * @param theTransactionDetails
+ * @param theTransactionDetails The current TransactionDetails object
*/
IResourceLookup findTargetResource(@Nonnull RequestPartitionId theRequestPartitionId, String theSourceResourceName, PathAndRef thePathAndRef, RequestDetails theRequest, TransactionDetails theTransactionDetails);
+ /**
+ * This method resolves the target of a reference found within a resource that is being created/updated. We do this
+ * so that we can create indexed links between resources, and so that we can validate that the target actually
+ * exists in cases where we need to check that.
+ *
+ * This method returns an {@link IResourceLookup} to avoid needing to resolve the entire resource.
+ *
+ * @param theRequestPartitionId The partition ID of the target resource
+ * @param theSourceResourceName The resource type for the resource containing the reference
+ * @param thePathAndRef The path and reference
+ * @param theRequest The incoming request, if any
+ * @param theTransactionDetails The current TransactionDetails object
+ */
+ @Nullable
+ IBaseResource loadTargetResource(@Nonnull RequestPartitionId theRequestPartitionId, String theSourceResourceName, PathAndRef thePathAndRef, RequestDetails theRequest, TransactionDetails theTransactionDetails);
+
+
+
void validateTypeOrThrowException(Class extends IBaseResource> theType);
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java
index cab5a806ea3..cfcbd2d0bc5 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java
@@ -44,31 +44,66 @@ import java.util.Set;
public interface ISearchParamExtractor {
-// SearchParamSet extractSearchParamCoords(IBaseResource theResource);
+ /**
+ * Constant for the {@literal theParamsToIndex} parameters on this interface
+ * indicating that all search parameters should be indexed.
+ */
+ Set ALL_PARAMS = Set.of("*");
- SearchParamSet extractSearchParamDates(IBaseResource theResource);
+ default SearchParamSet extractSearchParamDates(IBaseResource theResource) {
+ return extractSearchParamDates(theResource, ALL_PARAMS);
+ }
- SearchParamSet extractSearchParamNumber(IBaseResource theResource);
+ SearchParamSet extractSearchParamDates(IBaseResource theResource, Set theParamsToIndex);
- SearchParamSet extractSearchParamQuantity(IBaseResource theResource);
+ default SearchParamSet extractSearchParamNumber(IBaseResource theResource) {
+ return extractSearchParamNumber(theResource, ALL_PARAMS);
+ }
- SearchParamSet extractSearchParamQuantityNormalized(IBaseResource theResource);
+ SearchParamSet extractSearchParamNumber(IBaseResource theResource, Set theParamsToIndex);
- SearchParamSet extractSearchParamStrings(IBaseResource theResource);
+ default SearchParamSet extractSearchParamQuantity(IBaseResource theResource) {
+ return extractSearchParamQuantity(theResource, ALL_PARAMS);
+ }
- SearchParamSet extractSearchParamComposites(IBaseResource theResource);
+ SearchParamSet extractSearchParamQuantity(IBaseResource theResource, Set theParamsToIndex);
- SearchParamSet extractSearchParamTokens(IBaseResource theResource);
+ default SearchParamSet extractSearchParamQuantityNormalized(IBaseResource theResource) {
+ return extractSearchParamQuantityNormalized(theResource, ALL_PARAMS);
+ }
+
+ SearchParamSet extractSearchParamQuantityNormalized(IBaseResource theResource, Set theParamsToIndex);
+
+ default SearchParamSet extractSearchParamStrings(IBaseResource theResource) {
+ return extractSearchParamStrings(theResource, ALL_PARAMS);
+ }
+
+ SearchParamSet extractSearchParamStrings(IBaseResource theResource, Set theParamsToIndex);
+
+ default SearchParamSet extractSearchParamComposites(IBaseResource theResource) {
+ return extractSearchParamComposites(theResource, ALL_PARAMS);
+ }
+
+ SearchParamSet extractSearchParamComposites(IBaseResource theResource, Set theParamsToIndex);
+ default SearchParamSet extractSearchParamTokens(IBaseResource theResource) {
+ return extractSearchParamTokens(theResource, ALL_PARAMS);
+ }
+
+ SearchParamSet extractSearchParamTokens(IBaseResource theResource, Set theParamsToIndex);
SearchParamSet extractSearchParamTokens(IBaseResource theResource, RuntimeSearchParam theSearchParam);
- SearchParamSet extractSearchParamSpecial(IBaseResource theResource);
-
+ SearchParamSet extractSearchParamSpecial(IBaseResource theResource, Set theParamsToIndex);
SearchParamSet extractSearchParamComboUnique(String theResourceType, ResourceIndexedSearchParams theParams);
SearchParamSet extractSearchParamComboNonUnique(String theResourceType, ResourceIndexedSearchParams theParams);
- SearchParamSet extractSearchParamUri(IBaseResource theResource);
+ default SearchParamSet extractSearchParamUri(IBaseResource theResource) {
+ return extractSearchParamUri(theResource, ALL_PARAMS);
+ }
+
+
+ SearchParamSet extractSearchParamUri(IBaseResource theResource, Set theParamsToIndex);
SearchParamSet extractResourceLinks(IBaseResource theResource, boolean theWantLocalReferences);
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/PathAndRef.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/PathAndRef.java
index 92b84570db9..c735ee93a88 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/PathAndRef.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/PathAndRef.java
@@ -20,17 +20,21 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
* #L%
*/
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
import org.hl7.fhir.instance.model.api.IBaseReference;
+import org.hl7.fhir.instance.model.api.IBaseResource;
public class PathAndRef {
private final String myPath;
private final IBaseReference myRef;
+ private final IBaseResource myResource;
private final String mySearchParamName;
private final boolean myCanonical;
/**
- * Constructor
+ * Constructor for a reference
*/
public PathAndRef(String theSearchParamName, String thePath, IBaseReference theRef, boolean theCanonical) {
super();
@@ -38,6 +42,31 @@ public class PathAndRef {
myPath = thePath;
myRef = theRef;
myCanonical = theCanonical;
+ myResource = null;
+ }
+
+ /**
+ * Constructor for a resource (this is expected to be rare, only really covering
+ * cases like the path Bundle.entry.resource)
+ */
+ public PathAndRef(String theSearchParamName, String thePath, IBaseResource theResource) {
+ super();
+ mySearchParamName = theSearchParamName;
+ myPath = thePath;
+ myRef = null;
+ myCanonical = false;
+ myResource = theResource;
+ }
+
+ /**
+ * Note that this will generally be null, it is only used for cases like
+ * indexing {@literal Bundle.entry.resource}. If this is populated, {@link #getRef()}
+ * will be null and vice versa.
+ *
+ * @since 6.6.0
+ */
+ public IBaseResource getResource() {
+ return myResource;
}
public boolean isCanonical() {
@@ -52,8 +81,23 @@ public class PathAndRef {
return myPath;
}
+ /**
+ * If this is populated, {@link #getResource()} will be null, and vice versa.
+ */
public IBaseReference getRef() {
return myRef;
}
+ @Override
+ public String toString() {
+ ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ b.append("paramName", mySearchParamName);
+ if (myRef != null && myRef.getReferenceElement() != null) {
+ b.append("ref", myRef.getReferenceElement().getValue());
+ }
+ b.append("path", myPath);
+ b.append("resource", myResource);
+ b.append("canonical", myCanonical);
+ return b.toString();
+ }
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java
index 1c5c905a59e..83df7c9e25e 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java
@@ -133,15 +133,15 @@ public final class ResourceIndexedSearchParams {
theEntity.setResourceLinks(myLinks);
}
- public void updateSpnamePrefixForIndexedOnContainedResource(String theContainingType, String theSpnamePrefix) {
- updateSpnamePrefixForIndexedOnContainedResource(theContainingType, myNumberParams, theSpnamePrefix);
- updateSpnamePrefixForIndexedOnContainedResource(theContainingType, myQuantityParams, theSpnamePrefix);
- updateSpnamePrefixForIndexedOnContainedResource(theContainingType, myQuantityNormalizedParams, theSpnamePrefix);
- updateSpnamePrefixForIndexedOnContainedResource(theContainingType, myDateParams, theSpnamePrefix);
- updateSpnamePrefixForIndexedOnContainedResource(theContainingType, myUriParams, theSpnamePrefix);
- updateSpnamePrefixForIndexedOnContainedResource(theContainingType, myTokenParams, theSpnamePrefix);
- updateSpnamePrefixForIndexedOnContainedResource(theContainingType, myStringParams, theSpnamePrefix);
- updateSpnamePrefixForIndexedOnContainedResource(theContainingType, myCoordsParams, theSpnamePrefix);
+ public void updateSpnamePrefixForIndexOnUpliftedChain(String theContainingType, String theSpnamePrefix) {
+ updateSpnamePrefixForIndexOnUpliftedChain(theContainingType, myNumberParams, theSpnamePrefix);
+ updateSpnamePrefixForIndexOnUpliftedChain(theContainingType, myQuantityParams, theSpnamePrefix);
+ updateSpnamePrefixForIndexOnUpliftedChain(theContainingType, myQuantityNormalizedParams, theSpnamePrefix);
+ updateSpnamePrefixForIndexOnUpliftedChain(theContainingType, myDateParams, theSpnamePrefix);
+ updateSpnamePrefixForIndexOnUpliftedChain(theContainingType, myUriParams, theSpnamePrefix);
+ updateSpnamePrefixForIndexOnUpliftedChain(theContainingType, myTokenParams, theSpnamePrefix);
+ updateSpnamePrefixForIndexOnUpliftedChain(theContainingType, myStringParams, theSpnamePrefix);
+ updateSpnamePrefixForIndexOnUpliftedChain(theContainingType, myCoordsParams, theSpnamePrefix);
}
public void updateSpnamePrefixForLinksOnContainedResource(String theSpNamePrefix) {
@@ -176,7 +176,7 @@ public final class ResourceIndexedSearchParams {
}
}
- private void updateSpnamePrefixForIndexedOnContainedResource(String theContainingType, Collection extends BaseResourceIndexedSearchParam> theParams, @Nonnull String theSpnamePrefix) {
+ private void updateSpnamePrefixForIndexOnUpliftedChain(String theContainingType, Collection extends BaseResourceIndexedSearchParam> theParams, @Nonnull String theSpnamePrefix) {
for (BaseResourceIndexedSearchParam param : theParams) {
param.setResourceType(theContainingType);
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 f812b2b4200..2b15406115d 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
@@ -75,7 +75,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
public IValueExtractor getPathValueExtractor(IBase theResource, String theSinglePath) {
return () -> {
ExpressionNode parsed = myParsedFhirPathCache.get(theSinglePath, path -> myFhirPathEngine.parse(path));
- return myFhirPathEngine.evaluate((Base) theResource, parsed);
+ return myFhirPathEngine.evaluate(theResource, (Base) theResource, (Base) theResource, (Base) theResource, parsed);
};
}
@@ -98,13 +98,13 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
}
- private static class SearchParamExtractorR4HostServices implements FHIRPathEngine.IEvaluationContext {
+ private class SearchParamExtractorR4HostServices implements FHIRPathEngine.IEvaluationContext {
private final Map myResourceTypeToStub = Collections.synchronizedMap(new HashMap<>());
@Override
public List resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
- return null;
+ return Collections.emptyList();
}
@Override
@@ -135,6 +135,10 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
@Override
public Base resolveReference(Object theAppContext, String theUrl, Base theRefContext) throws FHIRException {
+ Base retVal = resolveResourceInBundleWithPlaceholderId(theAppContext, theUrl);
+ if (retVal != null) {
+ return retVal;
+ }
/*
* When we're doing resolution within the SearchParamExtractor, if we want
@@ -144,7 +148,6 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
* Encounter.patient.where(resolve() is Patient)
*/
IdType url = new IdType(theUrl);
- Base retVal = null;
if (isNotBlank(url.getResourceType())) {
retVal = myResourceTypeToStub.get(url.getResourceType());
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4B.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4B.java
index cac4ac93656..ae333e71e8c 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4B.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4B.java
@@ -74,7 +74,7 @@ public class SearchParamExtractorR4B extends BaseSearchParamExtractor implements
public IValueExtractor getPathValueExtractor(IBase theResource, String theSinglePath) {
return () -> {
ExpressionNode parsed = myParsedFhirPathCache.get(theSinglePath, path -> myFhirPathEngine.parse(path));
- return myFhirPathEngine.evaluate((Base) theResource, parsed);
+ return myFhirPathEngine.evaluate(theResource, (Base) theResource, (Base) theResource, (Base) theResource, parsed);
};
}
@@ -97,13 +97,13 @@ public class SearchParamExtractorR4B extends BaseSearchParamExtractor implements
}
- private static class SearchParamExtractorR4BHostServices implements FHIRPathEngine.IEvaluationContext {
+ private class SearchParamExtractorR4BHostServices implements FHIRPathEngine.IEvaluationContext {
private final Map myResourceTypeToStub = Collections.synchronizedMap(new HashMap<>());
@Override
public List resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
- return null;
+ return Collections.emptyList();
}
@Override
@@ -134,6 +134,10 @@ public class SearchParamExtractorR4B extends BaseSearchParamExtractor implements
@Override
public Base resolveReference(Object theAppContext, String theUrl, Base refContext) throws FHIRException {
+ Base retVal = resolveResourceInBundleWithPlaceholderId(theAppContext, theUrl);
+ if (retVal != null) {
+ return retVal;
+ }
/*
* When we're doing resolution within the SearchParamExtractor, if we want
@@ -143,7 +147,6 @@ public class SearchParamExtractorR4B extends BaseSearchParamExtractor implements
* Encounter.patient.where(resolve() is Patient)
*/
IdType url = new IdType(theUrl);
- Base retVal = null;
if (isNotBlank(url.getResourceType())) {
retVal = myResourceTypeToStub.get(url.getResourceType());
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java
index fd680f004f1..5df87cc9d5f 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java
@@ -26,9 +26,12 @@ import ca.uhn.fhir.jpa.model.entity.StorageSettings;
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 ca.uhn.fhir.util.bundle.BundleEntryParts;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.instance.model.api.IBase;
+import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r5.model.Base;
@@ -88,18 +91,18 @@ public class SearchParamExtractorR5 extends BaseSearchParamExtractor implements
public IValueExtractor getPathValueExtractor(IBase theResource, String theSinglePath) {
return () -> {
ExpressionNode parsed = myParsedFhirPathCache.get(theSinglePath, path -> myFhirPathEngine.parse(path));
- return myFhirPathEngine.evaluate((Base) theResource, parsed);
+ return myFhirPathEngine.evaluate(theResource, (Base) theResource, (Base) theResource, (Base) theResource, parsed);
};
}
- private static class SearchParamExtractorR5HostServices implements FHIRPathEngine.IEvaluationContext {
+ private class SearchParamExtractorR5HostServices implements FHIRPathEngine.IEvaluationContext {
private final Map myResourceTypeToStub = Collections.synchronizedMap(new HashMap<>());
@Override
public List resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
- return null;
+ return Collections.emptyList();
}
@Override
@@ -129,6 +132,10 @@ public class SearchParamExtractorR5 extends BaseSearchParamExtractor implements
@Override
public Base resolveReference(Object appContext, String theUrl, Base refContext) throws FHIRException {
+ Base retVal = resolveResourceInBundleWithPlaceholderId(appContext, theUrl);
+ if (retVal != null) {
+ return retVal;
+ }
/*
* When we're doing resolution within the SearchParamExtractor, if we want
@@ -138,7 +145,6 @@ public class SearchParamExtractorR5 extends BaseSearchParamExtractor implements
* Encounter.patient.where(resolve() is Patient)
*/
IdType url = new IdType(theUrl);
- Base retVal = null;
if (isNotBlank(url.getResourceType())) {
retVal = myResourceTypeToStub.get(url.getResourceType());
@@ -185,5 +191,4 @@ public class SearchParamExtractorR5 extends BaseSearchParamExtractor implements
}
-
}
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 8bdafc19604..bd2bf8e593e 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
@@ -35,7 +35,6 @@ import ca.uhn.fhir.jpa.model.dao.JpaPid;
import ca.uhn.fhir.jpa.model.entity.BasePartitionable;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.IResourceIndexComboSearchParameter;
-import ca.uhn.fhir.jpa.model.entity.StorageSettings;
import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique;
@@ -49,6 +48,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
+import ca.uhn.fhir.jpa.model.entity.StorageSettings;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.parser.DataFormatException;
@@ -67,6 +67,7 @@ import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
@@ -117,12 +118,22 @@ public class SearchParamExtractorService {
// All search parameter types except Reference
ResourceIndexedSearchParams normalParams = new ResourceIndexedSearchParams();
- extractSearchIndexParameters(theRequestDetails, normalParams, theResource);
+ extractSearchIndexParameters(theRequestDetails, normalParams, theResource, ISearchParamExtractor.ALL_PARAMS);
mergeParams(normalParams, theNewParams);
- if (myStorageSettings.isIndexOnContainedResources()) {
+ boolean indexOnContainedResources = myStorageSettings.isIndexOnContainedResources();
+ ISearchParamExtractor.SearchParamSet indexedReferences = mySearchParamExtractor.extractResourceLinks(theResource, indexOnContainedResources);
+ SearchParamExtractorService.handleWarnings(theRequestDetails, myInterceptorBroadcaster, indexedReferences);
+
+ if (indexOnContainedResources) {
ResourceIndexedSearchParams containedParams = new ResourceIndexedSearchParams();
- extractSearchIndexParametersForContainedResources(theRequestDetails, containedParams, theResource, theEntity);
+ extractSearchIndexParametersForContainedResources(theRequestDetails, containedParams, theResource, theEntity, indexedReferences);
+ mergeParams(containedParams, theNewParams);
+ }
+
+ if (myStorageSettings.isIndexOnUpliftedRefchains()) {
+ ResourceIndexedSearchParams containedParams = new ResourceIndexedSearchParams();
+ extractSearchIndexParametersForUpliftedRefchains(theRequestDetails, containedParams, theEntity, theRequestPartitionId, theTransactionDetails, indexedReferences);
mergeParams(containedParams, theNewParams);
}
@@ -130,9 +141,9 @@ public class SearchParamExtractorService {
populateResourceTables(theNewParams, theEntity);
// Reference search parameters
- extractResourceLinks(theRequestPartitionId, theExistingParams, theNewParams, theEntity, theResource, theTransactionDetails, theFailOnInvalidReference, theRequestDetails);
+ extractResourceLinks(theRequestPartitionId, theExistingParams, theNewParams, theEntity, theResource, theTransactionDetails, theFailOnInvalidReference, theRequestDetails, indexedReferences);
- if (myStorageSettings.isIndexOnContainedResources()) {
+ if (indexOnContainedResources) {
extractResourceLinksForContainedResources(theRequestPartitionId, theNewParams, theEntity, theResource, theTransactionDetails, theFailOnInvalidReference, theRequestDetails);
}
@@ -144,57 +155,156 @@ public class SearchParamExtractorService {
myStorageSettings = theStorageSettings;
}
- private void extractSearchIndexParametersForContainedResources(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, IBaseResource theResource, ResourceTable theEntity) {
+ /**
+ * Extract search parameter indexes for contained resources. E.g. if we
+ * are storing a Patient with a contained Organization, we might extract
+ * a String index on the Patient with paramName="organization.name" and
+ * value="Org Name"
+ */
+ private void extractSearchIndexParametersForContainedResources(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, IBaseResource theResource, ResourceTable theEntity, ISearchParamExtractor.SearchParamSet theIndexedReferences) {
FhirTerser terser = myContext.newTerser();
// 1. get all contained resources
Collection containedResources = terser.getAllEmbeddedResources(theResource, false);
- extractSearchIndexParametersForContainedResources(theRequestDetails, theParams, theResource, theEntity, containedResources, new HashSet<>());
+ // Extract search parameters
+ IChainedSearchParameterExtractionStrategy strategy = new IChainedSearchParameterExtractionStrategy() {
+ @Override
+ public Set getChainedSearchParametersToIndexForPath(PathAndRef thePathAndRef) {
+ // Currently for contained resources we always index all search parameters
+ // on all contained resources. A potential nice future optimization would
+ // be to make this configurable, perhaps with an optional extension you could
+ // add to a SearchParameter?
+ return ISearchParamExtractor.ALL_PARAMS;
+ }
+
+ @Override
+ public IBaseResource fetchResourceAtPath(PathAndRef thePathAndRef) {
+ return findContainedResource(containedResources, thePathAndRef.getRef());
+ }
+ };
+ boolean recurse = myStorageSettings.isIndexOnContainedResourcesRecursively();
+ extractSearchIndexParametersForTargetResources(theRequestDetails, theParams, theEntity, new HashSet<>(), strategy, theIndexedReferences, recurse, true);
}
- private void extractSearchIndexParametersForContainedResources(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, IBaseResource theResource, ResourceTable theEntity, Collection theContainedResources, Collection theAlreadySeenResources) {
+ /**
+ * Extract search parameter indexes for uplifted refchains. E.g. if we
+ * are storing a Patient with reference to an Organization and the
+ * "Patient:organization" SearchParameter declares an uplifted refchain
+ * on the "name" SearchParameter, we might extract a String index
+ * on the Patient with paramName="organization.name" and value="Org Name"
+ */
+ private void extractSearchIndexParametersForUpliftedRefchains(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, ResourceTable theEntity, RequestPartitionId theRequestPartitionId, TransactionDetails theTransactionDetails, ISearchParamExtractor.SearchParamSet theIndexedReferences) {
+ IChainedSearchParameterExtractionStrategy strategy = new IChainedSearchParameterExtractionStrategy() {
+
+ @Override
+ public Set getChainedSearchParametersToIndexForPath(PathAndRef thePathAndRef) {
+ String searchParamName = thePathAndRef.getSearchParamName();
+ RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(theEntity.getResourceType(), searchParamName);
+ return searchParam.getUpliftRefchainCodes();
+ }
+
+ @Override
+ public IBaseResource fetchResourceAtPath(PathAndRef thePathAndRef) {
+ // The PathAndRef will contain a resource if the SP path was inside a Bundle
+ // and pointed to a resource (e.g. Bundle.entry.resource) as opposed to
+ // pointing to a reference (e.g. Observation.subject)
+ if (thePathAndRef.getResource() != null) {
+ return thePathAndRef.getResource();
+ }
+
+ // Ok, it's a normal reference
+ IIdType reference = thePathAndRef.getRef().getReferenceElement();
+
+ // If we're processing a FHIR transaction, we store the resources
+ // mapped by their resolved resource IDs in theTransactionDetails
+ IBaseResource resolvedResource = theTransactionDetails.getResolvedResource(reference);
+
+ // And the usual case is that the reference points to a resource
+ // elsewhere in the repository, so we load it
+ if (resolvedResource == null && myResourceLinkResolver != null && !reference.getValue().startsWith("urn:uuid:")) {
+ RequestPartitionId targetRequestPartitionId = determineResolverPartitionId(theRequestPartitionId);
+ resolvedResource = myResourceLinkResolver.loadTargetResource(targetRequestPartitionId, theEntity.getResourceType(), thePathAndRef, theRequestDetails, theTransactionDetails);
+ if (resolvedResource != null) {
+ ourLog.trace("Found target: {}", resolvedResource.getIdElement());
+ theTransactionDetails.addResolvedResource(thePathAndRef.getRef().getReferenceElement(), resolvedResource);
+ }
+ }
+
+ return resolvedResource;
+ }
+ };
+ extractSearchIndexParametersForTargetResources(theRequestDetails, theParams, theEntity, new HashSet<>(), strategy, theIndexedReferences, false, false);
+ }
+
+ /**
+ * Extract indexes for contained references as well as for uplifted refchains.
+ * These two types of indexes are both similar special cases. Normally we handle
+ * chained searches ("Patient?organization.name=Foo") using a join from the
+ * {@link ResourceLink} table (for the "organization" part) to the
+ * {@link ResourceIndexedSearchParamString} table (for the "name" part). But
+ * for both contained resource indexes and uplifted refchains we use only the
+ * {@link ResourceIndexedSearchParamString} table to handle the entire
+ * "organization.name" part, or the other similar tables for token, number, etc.
+ *
+ * @see #extractSearchIndexParametersForContainedResources(RequestDetails, ResourceIndexedSearchParams, IBaseResource, ResourceTable, ISearchParamExtractor.SearchParamSet)
+ * @see #extractSearchIndexParametersForUpliftedRefchains(RequestDetails, ResourceIndexedSearchParams, ResourceTable, RequestPartitionId, TransactionDetails, ISearchParamExtractor.SearchParamSet)
+ */
+ private void extractSearchIndexParametersForTargetResources(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, ResourceTable theEntity, Collection theAlreadySeenResources, IChainedSearchParameterExtractionStrategy theTargetIndexingStrategy, ISearchParamExtractor.SearchParamSet theIndexedReferences, boolean theRecurse, boolean theIndexOnContainedResources) {
// 2. Find referenced search parameters
- ISearchParamExtractor.SearchParamSet referencedSearchParamSet = mySearchParamExtractor.extractResourceLinks(theResource, true);
String spnamePrefix;
- ResourceIndexedSearchParams currParams;
// 3. for each referenced search parameter, create an index
- for (PathAndRef nextPathAndRef : referencedSearchParamSet) {
+ for (PathAndRef nextPathAndRef : theIndexedReferences) {
// 3.1 get the search parameter name as spname prefix
spnamePrefix = nextPathAndRef.getSearchParamName();
- if (spnamePrefix == null || nextPathAndRef.getRef() == null)
+ if (spnamePrefix == null || (nextPathAndRef.getRef() == null && nextPathAndRef.getResource() == null))
continue;
- // 3.2 find the contained resource
- IBaseResource containedResource = findContainedResource(theContainedResources, nextPathAndRef.getRef());
- if (containedResource == null)
- continue;
-
- // 3.2.1 if we've already processed this resource upstream, do not process it again, to prevent infinite loops
- if (theAlreadySeenResources.contains(containedResource)) {
+ // 3.1.2 check if this ref actually applies here
+ Set searchParamsToIndex = theTargetIndexingStrategy.getChainedSearchParametersToIndexForPath(nextPathAndRef);
+ if (searchParamsToIndex.isEmpty()) {
continue;
}
- currParams = new ResourceIndexedSearchParams();
+ // 3.2 find the target resource
+ IBaseResource targetResource = theTargetIndexingStrategy.fetchResourceAtPath(nextPathAndRef);
+ if (targetResource == null)
+ continue;
+
+ // 3.2.1 if we've already processed this resource upstream, do not process it again, to prevent infinite loops
+ if (theAlreadySeenResources.contains(targetResource)) {
+ continue;
+ }
+
+ ResourceIndexedSearchParams currParams = new ResourceIndexedSearchParams();
// 3.3 create indexes for the current contained resource
- extractSearchIndexParameters(theRequestDetails, currParams, containedResource);
+ extractSearchIndexParameters(theRequestDetails, currParams, targetResource, searchParamsToIndex);
// 3.4 recurse to process any other contained resources referenced by this one
- if (myStorageSettings.isIndexOnContainedResourcesRecursively()) {
+ // Recursing is currently only allowed for contained resources and not
+ // uplifted refchains because the latter could potentially kill performance
+ // with the number of resource resolutions needed in order to handle
+ // a single write. Maybe in the future we could add caching to improve
+ // this
+ if (theRecurse) {
HashSet nextAlreadySeenResources = new HashSet<>(theAlreadySeenResources);
- nextAlreadySeenResources.add(containedResource);
- extractSearchIndexParametersForContainedResources(theRequestDetails, currParams, containedResource, theEntity, theContainedResources, nextAlreadySeenResources);
+ nextAlreadySeenResources.add(targetResource);
+
+ ISearchParamExtractor.SearchParamSet indexedReferences = mySearchParamExtractor.extractResourceLinks(targetResource, theIndexOnContainedResources);
+ SearchParamExtractorService.handleWarnings(theRequestDetails, myInterceptorBroadcaster, indexedReferences);
+
+ extractSearchIndexParametersForTargetResources(theRequestDetails, currParams, theEntity, nextAlreadySeenResources, theTargetIndexingStrategy, indexedReferences, true, theIndexOnContainedResources);
}
// 3.5 added reference name as a prefix for the contained resource if any
// e.g. for Observation.subject contained reference
// the SP_NAME = subject.family
- currParams.updateSpnamePrefixForIndexedOnContainedResource(theEntity.getResourceType(), spnamePrefix);
+ currParams.updateSpnamePrefixForIndexOnUpliftedChain(theEntity.getResourceType(), nextPathAndRef.getSearchParamName());
// 3.6 merge to the mainParams
// NOTE: the spname prefix is different
@@ -223,42 +333,42 @@ public class SearchParamExtractorService {
theTargetParams.myCompositeParams.addAll(theSrcParams.myCompositeParams);
}
- void extractSearchIndexParameters(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, IBaseResource theResource) {
+ void extractSearchIndexParameters(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, IBaseResource theResource, Set theParamsToIndex) {
// Strings
- ISearchParamExtractor.SearchParamSet strings = extractSearchParamStrings(theResource);
+ ISearchParamExtractor.SearchParamSet strings = extractSearchParamStrings(theResource, theParamsToIndex);
handleWarnings(theRequestDetails, myInterceptorBroadcaster, strings);
theParams.myStringParams.addAll(strings);
// Numbers
- ISearchParamExtractor.SearchParamSet numbers = extractSearchParamNumber(theResource);
+ ISearchParamExtractor.SearchParamSet numbers = extractSearchParamNumber(theResource, theParamsToIndex);
handleWarnings(theRequestDetails, myInterceptorBroadcaster, numbers);
theParams.myNumberParams.addAll(numbers);
// Quantities
- ISearchParamExtractor.SearchParamSet quantities = extractSearchParamQuantity(theResource);
+ ISearchParamExtractor.SearchParamSet quantities = extractSearchParamQuantity(theResource, theParamsToIndex);
handleWarnings(theRequestDetails, myInterceptorBroadcaster, quantities);
theParams.myQuantityParams.addAll(quantities);
if (myStorageSettings.getNormalizedQuantitySearchLevel().equals(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_STORAGE_SUPPORTED) || myStorageSettings.getNormalizedQuantitySearchLevel().equals(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED)) {
- ISearchParamExtractor.SearchParamSet quantitiesNormalized = extractSearchParamQuantityNormalized(theResource);
+ ISearchParamExtractor.SearchParamSet quantitiesNormalized = extractSearchParamQuantityNormalized(theResource, theParamsToIndex);
handleWarnings(theRequestDetails, myInterceptorBroadcaster, quantitiesNormalized);
theParams.myQuantityNormalizedParams.addAll(quantitiesNormalized);
}
// Dates
- ISearchParamExtractor.SearchParamSet dates = extractSearchParamDates(theResource);
+ ISearchParamExtractor.SearchParamSet dates = extractSearchParamDates(theResource, theParamsToIndex);
handleWarnings(theRequestDetails, myInterceptorBroadcaster, dates);
theParams.myDateParams.addAll(dates);
// URIs
- ISearchParamExtractor.SearchParamSet uris = extractSearchParamUri(theResource);
+ ISearchParamExtractor.SearchParamSet uris = extractSearchParamUri(theResource, theParamsToIndex);
handleWarnings(theRequestDetails, myInterceptorBroadcaster, uris);
theParams.myUriParams.addAll(uris);
// Tokens (can result in both Token and String, as we index the display name for
// the types: Coding, CodeableConcept)
- ISearchParamExtractor.SearchParamSet tokens = extractSearchParamTokens(theResource);
+ ISearchParamExtractor.SearchParamSet tokens = extractSearchParamTokens(theResource, theParamsToIndex);
for (BaseResourceIndexedSearchParam next : tokens) {
if (next instanceof ResourceIndexedSearchParamToken) {
theParams.myTokenParams.add((ResourceIndexedSearchParamToken) next);
@@ -272,13 +382,13 @@ public class SearchParamExtractorService {
// Composites
// dst2 composites use stuff like value[x] , and we don't support them.
if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
- ISearchParamExtractor.SearchParamSet composites = extractSearchParamComposites(theResource);
+ ISearchParamExtractor.SearchParamSet composites = extractSearchParamComposites(theResource, theParamsToIndex);
handleWarnings(theRequestDetails, myInterceptorBroadcaster, composites);
theParams.myCompositeParams.addAll(composites);
}
// Specials
- ISearchParamExtractor.SearchParamSet specials = extractSearchParamSpecial(theResource);
+ ISearchParamExtractor.SearchParamSet specials = extractSearchParamSpecial(theResource, theParamsToIndex);
for (BaseResourceIndexedSearchParam next : specials) {
if (next instanceof ResourceIndexedSearchParamCoords) {
theParams.myCoordsParams.add((ResourceIndexedSearchParamCoords) next);
@@ -304,19 +414,22 @@ public class SearchParamExtractorService {
myContext = theContext;
}
- private void extractResourceLinks(RequestPartitionId theRequestPartitionId, ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, TransactionDetails theTransactionDetails, boolean theFailOnInvalidReference, RequestDetails theRequest) {
- extractResourceLinks(theRequestPartitionId, new ResourceIndexedSearchParams(), theParams, theEntity, theResource, theTransactionDetails, theFailOnInvalidReference, theRequest);
+ private void extractResourceLinks(RequestPartitionId theRequestPartitionId, ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, TransactionDetails theTransactionDetails, boolean theFailOnInvalidReference, RequestDetails theRequest, ISearchParamExtractor.SearchParamSet theIndexedReferences) {
+ extractResourceLinks(theRequestPartitionId, new ResourceIndexedSearchParams(), theParams, theEntity, theResource, theTransactionDetails, theFailOnInvalidReference, theRequest, theIndexedReferences);
}
- private void extractResourceLinks(RequestPartitionId theRequestPartitionId, ResourceIndexedSearchParams theExistingParams, ResourceIndexedSearchParams theNewParams, ResourceTable theEntity, IBaseResource theResource, TransactionDetails theTransactionDetails, boolean theFailOnInvalidReference, RequestDetails theRequest) {
+ private void extractResourceLinks(RequestPartitionId theRequestPartitionId, ResourceIndexedSearchParams theExistingParams, ResourceIndexedSearchParams theNewParams, ResourceTable theEntity, IBaseResource theResource, TransactionDetails theTransactionDetails, boolean theFailOnInvalidReference, RequestDetails theRequest, ISearchParamExtractor.SearchParamSet theIndexedReferences) {
String sourceResourceName = myContext.getResourceType(theResource);
- ISearchParamExtractor.SearchParamSet refs = mySearchParamExtractor.extractResourceLinks(theResource, false);
- SearchParamExtractorService.handleWarnings(theRequest, myInterceptorBroadcaster, refs);
+ for (PathAndRef nextPathAndRef : theIndexedReferences) {
+ if (nextPathAndRef.getRef() != null) {
+ if (nextPathAndRef.getRef().getReferenceElement().isLocal()) {
+ continue;
+ }
- for (PathAndRef nextPathAndRef : refs) {
- RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(sourceResourceName, nextPathAndRef.getSearchParamName());
- extractResourceLinks(theRequestPartitionId, theExistingParams, theNewParams, theEntity, theTransactionDetails, sourceResourceName, searchParam, nextPathAndRef, theFailOnInvalidReference, theRequest);
+ RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(sourceResourceName, nextPathAndRef.getSearchParamName());
+ extractResourceLinks(theRequestPartitionId, theExistingParams, theNewParams, theEntity, theTransactionDetails, sourceResourceName, searchParam, nextPathAndRef, theFailOnInvalidReference, theRequest);
+ }
}
theEntity.setHasLinks(theNewParams.myLinks.size() > 0);
@@ -531,7 +644,8 @@ public class SearchParamExtractorService {
currParams = new ResourceIndexedSearchParams();
// 3.3 create indexes for the current contained resource
- extractResourceLinks(theRequestPartitionId, currParams, theEntity, containedResource, theTransactionDetails, theFailOnInvalidReference, theRequest);
+ ISearchParamExtractor.SearchParamSet indexedReferences = mySearchParamExtractor.extractResourceLinks(containedResource, true);
+ extractResourceLinks(theRequestPartitionId, currParams, theEntity, containedResource, theTransactionDetails, theFailOnInvalidReference, theRequest, indexedReferences);
// 3.4 recurse to process any other contained resources referenced by this one
if (myStorageSettings.isIndexOnContainedResourcesRecursively()) {
@@ -602,6 +716,14 @@ public class SearchParamExtractorService {
return ResourceLink.forLocalReference(thePathAndRef.getPath(), theEntity, targetResourceType, targetResourcePid, targetResourceIdPart, theUpdateTime, targetVersion);
}
+ private RequestPartitionId determineResolverPartitionId(@Nonnull RequestPartitionId theRequestPartitionId) {
+ RequestPartitionId targetRequestPartitionId = theRequestPartitionId;
+ if (myPartitionSettings.isPartitioningEnabled() && myPartitionSettings.getAllowReferencesAcrossPartitions() == PartitionSettings.CrossPartitionReferenceMode.ALLOWED_UNQUALIFIED) {
+ targetRequestPartitionId = RequestPartitionId.allPartitions();
+ }
+ return targetRequestPartitionId;
+ }
+
private void populateResourceTable(Collection extends BaseResourceIndexedSearchParam> theParams, ResourceTable theResourceTable) {
for (BaseResourceIndexedSearchParam next : theParams) {
if (next.getResourcePid() == null) {
@@ -621,43 +743,42 @@ public class SearchParamExtractorService {
}
}
- private ISearchParamExtractor.SearchParamSet extractSearchParamDates(IBaseResource theResource) {
- return mySearchParamExtractor.extractSearchParamDates(theResource);
+ private ISearchParamExtractor.SearchParamSet extractSearchParamDates(IBaseResource theResource, Set theParamsToIndex) {
+ return mySearchParamExtractor.extractSearchParamDates(theResource, theParamsToIndex);
}
- private ISearchParamExtractor.SearchParamSet extractSearchParamNumber(IBaseResource theResource) {
- return mySearchParamExtractor.extractSearchParamNumber(theResource);
+ private ISearchParamExtractor.SearchParamSet extractSearchParamNumber(IBaseResource theResource, Set theParamsToIndex) {
+ return mySearchParamExtractor.extractSearchParamNumber(theResource, theParamsToIndex);
}
- private ISearchParamExtractor.SearchParamSet extractSearchParamQuantity(IBaseResource theResource) {
- return mySearchParamExtractor.extractSearchParamQuantity(theResource);
+ private ISearchParamExtractor.SearchParamSet extractSearchParamQuantity(IBaseResource theResource, Set theParamsToIndex) {
+ return mySearchParamExtractor.extractSearchParamQuantity(theResource, theParamsToIndex);
}
- private ISearchParamExtractor.SearchParamSet extractSearchParamQuantityNormalized(IBaseResource theResource) {
- return mySearchParamExtractor.extractSearchParamQuantityNormalized(theResource);
+ private ISearchParamExtractor.SearchParamSet