001/*-
002 * #%L
003 * HAPI FHIR JPA Server
004 * %%
005 * Copyright (C) 2014 - 2026 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.jpa.bulk.export.svc;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.interceptor.model.RequestPartitionId;
024import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
025import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
026import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
027import ca.uhn.fhir.jpa.api.svc.ResolveIdentityMode;
028import ca.uhn.fhir.jpa.model.dao.JpaPid;
029import ca.uhn.fhir.mdm.svc.IBulkExportMdmEidMatchOnlyResourceExpander;
030import ca.uhn.fhir.mdm.svc.IBulkExportMdmResourceExpander;
031import ca.uhn.fhir.mdm.svc.MdmEidMatchOnlyExpandSvc;
032import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
033import ca.uhn.fhir.util.FhirTerser;
034import org.hl7.fhir.instance.model.api.IBaseReference;
035import org.hl7.fhir.instance.model.api.IBaseResource;
036import org.hl7.fhir.instance.model.api.IIdType;
037
038import java.util.HashSet;
039import java.util.List;
040import java.util.Set;
041import java.util.stream.Collectors;
042
043/**
044 * Implementation of {@link IBulkExportMdmResourceExpander} that handles bulk export resource expansion
045 * when MDM mode is Match-Only and Eid Systems defined in mdm rules.
046 *
047 * <p>This expander is used during bulk export operations to expand Group resources by resolving
048 * MDM matching resources for the members in the group. Resources are
049 * matched based on just eids rather than the full MDM golden resource relationships.</p>
050 */
051public class BulkExportMdmEidMatchOnlyResourceExpander implements IBulkExportMdmEidMatchOnlyResourceExpander<JpaPid> {
052
053        private final DaoRegistry myDaoRegistry;
054        private final MdmEidMatchOnlyExpandSvc myMdmEidMatchOnlyLinkExpandSvc;
055        private final FhirContext myFhirContext;
056        private final IIdHelperService<JpaPid> myIdHelperService;
057
058        /**
059         * Constructor
060         */
061        public BulkExportMdmEidMatchOnlyResourceExpander(
062                        DaoRegistry theDaoRegistry,
063                        MdmEidMatchOnlyExpandSvc theMdmEidMatchOnlyLinkExpandSvc,
064                        FhirContext theFhirContext,
065                        IIdHelperService<JpaPid> theIdHelperService) {
066                myDaoRegistry = theDaoRegistry;
067                myMdmEidMatchOnlyLinkExpandSvc = theMdmEidMatchOnlyLinkExpandSvc;
068                myFhirContext = theFhirContext;
069                myIdHelperService = theIdHelperService;
070        }
071
072        /**
073         * Expands a Group resource and returns the Group members' resource persistent ids.
074         * The returned ids consists of group members + all MDM matched resources based on EID only.
075         *
076         * <p>This method:</p>
077         * <ol>
078         *   <li>Reads the specified Group resource</li>
079         *   <li>Extracts all member entity references from the Group</li>
080         *   <li>For each member, uses EID matching to find all resources that have the same EID as the member, using eid system specified in mdm rules</li>
081         *   <li>Converts the expanded resource IDs to persistent IDs (PIDs)</li>
082         * </ol>
083         *
084         * @param groupResourceId The ID of the Group resource to expand
085         * @param requestPartitionId The request partition ID
086         * @return A set of {@link JpaPid} objects representing all expanded resources
087         */
088        @Override
089        public Set<JpaPid> expandGroup(String groupResourceId, RequestPartitionId requestPartitionId) {
090                // Read the Group resource
091                SystemRequestDetails srd = SystemRequestDetails.forRequestPartitionId(requestPartitionId);
092                IIdType groupId = myFhirContext.getVersion().newIdType(groupResourceId);
093                IFhirResourceDao<?> groupDao = myDaoRegistry.getResourceDao("Group");
094                IBaseResource groupResource = groupDao.read(groupId, srd);
095
096                Set<String> allResourceIds = new HashSet<>();
097                FhirTerser terser = myFhirContext.newTerser();
098                // Extract all member.entity references from the Group resource
099                List<IBaseReference> memberEntities =
100                                terser.getValues(groupResource, "Group.member.entity", IBaseReference.class);
101                // mdm expand each member based on eid
102                for (IBaseReference entityRef : memberEntities) {
103                        if (!entityRef.getReferenceElement().isEmpty()) {
104                                IIdType memberId = entityRef.getReferenceElement();
105                                Set<String> expanded =
106                                                myMdmEidMatchOnlyLinkExpandSvc.expandMdmBySourceResourceId(requestPartitionId, memberId);
107                                allResourceIds.addAll(expanded);
108                        }
109                }
110                // Convert all resourceIds to IIdType and resolve in batch
111                List<IIdType> idTypes = allResourceIds.stream()
112                                .map(id -> myFhirContext.getVersion().newIdType(id))
113                                .collect(Collectors.toList());
114                List<JpaPid> pidList = myIdHelperService.resolveResourcePids(
115                                requestPartitionId,
116                                idTypes,
117                                ResolveIdentityMode.excludeDeleted().cacheOk());
118                return new HashSet<>(pidList);
119        }
120
121        /**
122         * Expands a single patient ID to include all patients linked via EID matching.
123         *
124         * @param thePatientId Patient ID to expand (e.g., "Patient/123")
125         * @param theRequestPartitionId Partition context for the request
126         * @return Set of String patient IDs including the original patient and all EID-matched patients
127         */
128        @Override
129        public Set<String> expandPatient(String thePatientId, RequestPartitionId theRequestPartitionId) {
130                IIdType patientIdType =
131                                myFhirContext.getVersion().newIdType(thePatientId).withResourceType("Patient");
132                return myMdmEidMatchOnlyLinkExpandSvc.expandMdmBySourceResourceId(theRequestPartitionId, patientIdType);
133        }
134
135        @Override
136        public void annotateResource(IBaseResource resource) {
137                // This function is normally used to add golden resource id to the exported resources,
138                // but in the Eid-based match only mode, there isn't any golden resource, so nothing to do here
139        }
140}