This commit is contained in:
James Agnew 2024-11-13 12:54:47 -05:00
parent 1c13ccd2d5
commit 034d9a9b48
8 changed files with 80 additions and 39 deletions

View File

@ -0,0 +1,8 @@
---
type: add
issue: 6464
title: "A new experimental interceptor called the MdmReadVirtualizationInterceptor
has been added. This interceptor rewrites results when querying an MDM-enabled
JPA server in order to always include linked resources and rerwrites query results
to link to the MDM golden resource. This interceptor is still being developed
and should be used with caution."

View File

@ -134,7 +134,8 @@ public interface IMdmLinkJpaRepository
+ " lookup_link.mySourcePid IN (:pids))"
+ "AND lookup_link.myMatchResult = :matchResult")
List<MdmPidTuple> expandPidsByGoldenResourcePidsOrSourcePidsAndMatchResult(
@Param("pids") Collection<Long> theSourcePid, @Param("matchResult") MdmMatchResultEnum theMdmMatchResultEnum);
@Param("pids") Collection<Long> theSourcePid,
@Param("matchResult") MdmMatchResultEnum theMdmMatchResultEnum);
@Query(
"SELECT ml.myId FROM MdmLink ml WHERE ml.myMdmSourceType = :resourceName AND ml.myCreated <= :highThreshold ORDER BY ml.myCreated DESC")

View File

@ -37,7 +37,6 @@ import ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters;
import ca.uhn.fhir.mdm.dao.IMdmLinkDao;
import ca.uhn.fhir.mdm.model.MdmPidTuple;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import jakarta.annotation.Nonnull;
import jakarta.persistence.EntityManager;
@ -155,11 +154,12 @@ public class MdmLinkDaoJpaImpl implements IMdmLinkDao<JpaPid, MdmLink> {
@Override
public Collection<MdmPidTuple<JpaPid>> resolveGoldenResources(List<JpaPid> theSourcePids) {
return myMdmLinkDao
.expandPidsByGoldenResourcePidsOrSourcePidsAndMatchResult(JpaPid.toLongList(theSourcePids), MdmMatchResultEnum.MATCH)
.stream()
.map(this::daoTupleToMdmTuple)
.distinct()
.collect(Collectors.toList());
.expandPidsByGoldenResourcePidsOrSourcePidsAndMatchResult(
JpaPid.toLongList(theSourcePids), MdmMatchResultEnum.MATCH)
.stream()
.map(this::daoTupleToMdmTuple)
.distinct()
.collect(Collectors.toList());
}
@Override

View File

@ -22,8 +22,8 @@ package ca.uhn.fhir.jpa.mdm.config;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.nickname.INicknameSvc;
import ca.uhn.fhir.mdm.api.IMdmSettings;
import ca.uhn.fhir.mdm.interceptor.MdmSearchExpandingInterceptor;
import ca.uhn.fhir.mdm.interceptor.MdmReadVirtualizationInterceptor;
import ca.uhn.fhir.mdm.interceptor.MdmSearchExpandingInterceptor;
import ca.uhn.fhir.mdm.rules.config.MdmRuleValidator;
import ca.uhn.fhir.mdm.rules.matcher.IMatcherFactory;
import ca.uhn.fhir.mdm.rules.matcher.MdmMatcherFactory;

View File

@ -20,7 +20,6 @@
package ca.uhn.fhir.mdm.dao;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.model.dao.JpaPid;
import ca.uhn.fhir.mdm.api.IMdmLink;
import ca.uhn.fhir.mdm.api.MdmLinkSourceEnum;
import ca.uhn.fhir.mdm.api.MdmLinkWithRevision;

View File

@ -31,27 +31,54 @@ import java.util.List;
import java.util.Optional;
import java.util.Set;
/**
* <b>This class is experimental and subject to change. Use with caution.</b>
* <p>
* This interceptor provided an "MDM Virtualized" endpoint, meaning that
* searches are expanded to include MDM-linked resources (including any
* linked golden resource, and also including any other resources linked
* to that golden resource). Searches for non-MDM resources which have
* a reference to an MDM resource will have their reference parameter
* expanded to include the golden and linked resources.
* </p>
* <p>
* In addition, responses are cleaned up so that only the golden resource
* is included in responses, and references to non-golden resources
* are rewritten.
* </p>
* <p>
* This interceptor does not modify data that is being stored/written
* in any way, it only modifies data that is being returned by the
* server.
* </p>
*
* @since 8.0.0
*/
public class MdmReadVirtualizationInterceptor<P extends IResourcePersistentId<?>> {
@Autowired
private FhirContext myFhirContext;
@Autowired
private IMdmLinkDao<P, ?> myMdmLinkDao;
@Autowired
private IIdHelperService<P> myIdHelperService;
@Autowired
private DaoRegistry myDaoRegistry;
@Autowired
private MdmSettings myMdmSettings;
@Autowired
private MdmSearchExpansionSvc myMdmSearchExpansionSvc;
@Hook(Pointcut.STORAGE_PRESEARCH_REGISTERED)
public void hook(RequestDetails theRequestDetails, SearchParameterMap theSearchParameterMap) {
myMdmSearchExpansionSvc.expandSearch(theRequestDetails, theSearchParameterMap, t->true);
myMdmSearchExpansionSvc.expandSearch(theRequestDetails, theSearchParameterMap, t -> true);
}
@Hook(Pointcut.STORAGE_PRESHOW_RESOURCES)
public void preShowResources(RequestDetails theRequestDetails, IPreResourceShowDetails theDetails) {
@ -65,14 +92,16 @@ public class MdmReadVirtualizationInterceptor<P extends IResourcePersistentId<?>
Set<IIdType> allIds = new HashSet<>();
candidateResourceIds.keySet().forEach(t -> allIds.add(newIdType(t)));
candidateReferences.keySet().forEach(t -> allIds.add(newIdType(t)));
List<P> sourcePids = myIdHelperService.getPidsOrThrowException(RequestPartitionId.allPartitions(), List.copyOf(allIds));
List<P> sourcePids =
myIdHelperService.getPidsOrThrowException(RequestPartitionId.allPartitions(), List.copyOf(allIds));
Collection<MdmPidTuple<P>> tuples = myMdmLinkDao.resolveGoldenResources(sourcePids);
// Resolve the link PIDs into FHIR IDs
Set<P> allPersistentIds = new HashSet<>();
tuples.forEach(t -> allPersistentIds.add(t.getGoldenPid()));
tuples.forEach(t -> allPersistentIds.add(t.getSourcePid()));
PersistentIdToForcedIdMap<P> persistentIdToFhirIdMap = myIdHelperService.translatePidsToForcedIds(allPersistentIds);
PersistentIdToForcedIdMap<P> persistentIdToFhirIdMap =
myIdHelperService.translatePidsToForcedIds(allPersistentIds);
// Loop through each link and figure out whether we need to remap anything
for (MdmPidTuple<P> tuple : tuples) {
@ -84,13 +113,13 @@ public class MdmReadVirtualizationInterceptor<P extends IResourcePersistentId<?>
List<ResourceReferenceInfo> referencesToRemap = candidateReferences.get(sourceId);
if (!referencesToRemap.isEmpty()) {
P associatedGoldenResourcePid = tuple.getGoldenPid();
Optional<String> associatedGoldenResourceId = persistentIdToFhirIdMap.get(associatedGoldenResourcePid);
Optional<String> associatedGoldenResourceId =
persistentIdToFhirIdMap.get(associatedGoldenResourcePid);
if (associatedGoldenResourceId.isPresent()) {
for (ResourceReferenceInfo referenceInfoToRemap : referencesToRemap) {
IBaseReference referenceToRemap = referenceInfoToRemap.getResourceReference();
referenceToRemap.setReference(associatedGoldenResourceId.get());
}
}
}
@ -122,7 +151,6 @@ public class MdmReadVirtualizationInterceptor<P extends IResourcePersistentId<?>
}
}
}
}
/**
@ -130,7 +158,8 @@ public class MdmReadVirtualizationInterceptor<P extends IResourcePersistentId<?>
* that resource within the {@link IPreResourceShowDetails}
*/
private ListMultimap<String, Integer> extractRemapCandidateResources(IPreResourceShowDetails theDetails) {
ListMultimap<String, Integer> retVal = MultimapBuilder.hashKeys().arrayListValues().build();
ListMultimap<String, Integer> retVal =
MultimapBuilder.hashKeys().arrayListValues().build();
for (int resourceIdx = 0; resourceIdx < theDetails.size(); resourceIdx++) {
IBaseResource resource = theDetails.getResource(resourceIdx);
@ -149,8 +178,10 @@ public class MdmReadVirtualizationInterceptor<P extends IResourcePersistentId<?>
* @return Returns a map where the keys are a typed ID (Patient/ABC) and the values are references
* found in any of the resources that are referring to that ID.
*/
private ListMultimap<String, ResourceReferenceInfo> extractRemapCandidateReferences(IPreResourceShowDetails theDetails) {
ListMultimap<String, ResourceReferenceInfo> retVal = MultimapBuilder.hashKeys().arrayListValues().build();
private ListMultimap<String, ResourceReferenceInfo> extractRemapCandidateReferences(
IPreResourceShowDetails theDetails) {
ListMultimap<String, ResourceReferenceInfo> retVal =
MultimapBuilder.hashKeys().arrayListValues().build();
FhirTerser terser = myFhirContext.newTerser();
for (int resourceIdx = 0; resourceIdx < theDetails.size(); resourceIdx++) {
@ -182,6 +213,4 @@ public class MdmReadVirtualizationInterceptor<P extends IResourcePersistentId<?>
private boolean isRemapCandidate(String theResourceType) {
return myMdmSettings.isSupportedMdmType(theResourceType);
}
}

View File

@ -49,5 +49,4 @@ public class MdmSearchExpandingInterceptor {
myMdmSearchExpansionSvc.expandSearch(theRequestDetails, theSearchParameterMap, ReferenceParam::isMdmExpand);
}
}
}

View File

@ -51,12 +51,15 @@ public class MdmSearchExpansionSvc {
* by this function to determine whether it should be expanded.
* @since 8.0.0
*/
public void expandSearch(RequestDetails theRequestDetails, SearchParameterMap theSearchParameterMap, Function<ReferenceParam, Boolean> theExpansionCandidateTester) {
public void expandSearch(
RequestDetails theRequestDetails,
SearchParameterMap theSearchParameterMap,
Function<ReferenceParam, Boolean> theExpansionCandidateTester) {
final RequestDetails requestDetailsToUse =
theRequestDetails == null ? new SystemRequestDetails() : theRequestDetails;
theRequestDetails == null ? new SystemRequestDetails() : theRequestDetails;
final RequestPartitionId requestPartitionId =
myRequestPartitionHelperSvc.determineReadPartitionForRequestForSearchType(
requestDetailsToUse, requestDetailsToUse.getResourceName(), theSearchParameterMap);
myRequestPartitionHelperSvc.determineReadPartitionForRequestForSearchType(
requestDetailsToUse, requestDetailsToUse.getResourceName(), theSearchParameterMap);
for (Map.Entry<String, List<List<IQueryParameterType>>> set : theSearchParameterMap.entrySet()) {
String paramName = set.getKey();
List<List<IQueryParameterType>> andList = set.getValue();
@ -69,7 +72,10 @@ public class MdmSearchExpansionSvc {
}
private void expandAnyReferenceParameters(
RequestPartitionId theRequestPartitionId, String theParamName, List<IQueryParameterType> orList, Function<ReferenceParam, Boolean> theExpansionCandidateTester) {
RequestPartitionId theRequestPartitionId,
String theParamName,
List<IQueryParameterType> orList,
Function<ReferenceParam, Boolean> theExpansionCandidateTester) {
List<IQueryParameterType> toRemove = new ArrayList<>();
List<IQueryParameterType> toAdd = new ArrayList<>();
for (IQueryParameterType iQueryParameterType : orList) {
@ -79,12 +85,12 @@ public class MdmSearchExpansionSvc {
ourLog.debug("Found a reference parameter to expand: {}", refParam);
// First, attempt to expand as a source resource.
Set<String> expandedResourceIds = myMdmLinkExpandSvc.expandMdmBySourceResourceId(
theRequestPartitionId, new IdDt(refParam.getValue()));
theRequestPartitionId, new IdDt(refParam.getValue()));
// If we failed, attempt to expand as a golden resource
if (expandedResourceIds.isEmpty()) {
expandedResourceIds = myMdmLinkExpandSvc.expandMdmByGoldenResourceId(
theRequestPartitionId, new IdDt(refParam.getValue()));
theRequestPartitionId, new IdDt(refParam.getValue()));
}
// Rebuild the search param list.
@ -92,9 +98,9 @@ public class MdmSearchExpansionSvc {
ourLog.debug("Parameter has been expanded to: {}", String.join(", ", expandedResourceIds));
toRemove.add(refParam);
expandedResourceIds.stream()
.map(resourceId -> addResourceTypeIfNecessary(refParam.getResourceType(), resourceId))
.map(ReferenceParam::new)
.forEach(toAdd::add);
.map(resourceId -> addResourceTypeIfNecessary(refParam.getResourceType(), resourceId))
.map(ReferenceParam::new)
.forEach(toAdd::add);
}
}
} else if (theParamName.equalsIgnoreCase("_id")) {
@ -124,10 +130,10 @@ public class MdmSearchExpansionSvc {
* @param theRemoveList
*/
private void expandIdParameter(
RequestPartitionId theRequestPartitionId,
IQueryParameterType theIdParameter,
List<IQueryParameterType> theAddList,
List<IQueryParameterType> theRemoveList) {
RequestPartitionId theRequestPartitionId,
IQueryParameterType theIdParameter,
List<IQueryParameterType> theAddList,
List<IQueryParameterType> theRemoveList) {
// id parameters can either be StringParam (for $everything operation)
// or TokenParam (for searches)
// either case, we want to expand it out and grab all related resources
@ -147,8 +153,8 @@ public class MdmSearchExpansionSvc {
if (id == null) {
// in case the _id parameter type is different from the above
ourLog.warn(
"_id parameter of incorrect type. Expected StringParam or TokenParam, but got {}. No expansion will be done!",
theIdParameter.getClass().getSimpleName());
"_id parameter of incorrect type. Expected StringParam or TokenParam, but got {}. No expansion will be done!",
theIdParameter.getClass().getSimpleName());
} else if (mdmExpand) {
ourLog.debug("_id parameter must be expanded out from: {}", id.getValue());
@ -171,5 +177,4 @@ public class MdmSearchExpansionSvc {
}
// else - no expansion required
}
}