001/*-
002 * #%L
003 * HAPI FHIR - Master Data Management
004 * %%
005 * Copyright (C) 2014 - 2025 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.mdm.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.rest.api.server.SystemRequestDetails;
030import ca.uhn.fhir.util.FhirTerser;
031import org.hl7.fhir.instance.model.api.IBaseReference;
032import org.hl7.fhir.instance.model.api.IBaseResource;
033import org.hl7.fhir.instance.model.api.IIdType;
034
035import java.util.HashSet;
036import java.util.List;
037import java.util.Set;
038import java.util.stream.Collectors;
039
040/**
041 * Implementation of {@link IBulkExportMdmResourceExpander} that handles bulk export resource expansion
042 * when MDM mode is Match-Only and Eid Systems defined in mdm rules.
043 *
044 * <p>This expander is used during bulk export operations to expand Group resources by resolving
045 * MDM matching resources for the members in the group. Resources are
046 * matched based on just eids rather than the full MDM golden resource relationships.</p>
047 */
048public class BulkExportMdmEidMatchOnlyResourceExpander implements IBulkExportMdmResourceExpander {
049
050        private final DaoRegistry myDaoRegistry;
051        private final MdmEidMatchOnlyExpandSvc myMdmEidMatchOnlyLinkExpandSvc;
052        private final FhirContext myFhirContext;
053        private final IIdHelperService<JpaPid> myIdHelperService;
054
055        /**
056         * Constructor
057         */
058        public BulkExportMdmEidMatchOnlyResourceExpander(
059                        DaoRegistry theDaoRegistry,
060                        MdmEidMatchOnlyExpandSvc theMdmEidMatchOnlyLinkExpandSvc,
061                        FhirContext theFhirContext,
062                        IIdHelperService<JpaPid> theIdHelperService) {
063                myDaoRegistry = theDaoRegistry;
064                myMdmEidMatchOnlyLinkExpandSvc = theMdmEidMatchOnlyLinkExpandSvc;
065                myFhirContext = theFhirContext;
066                myIdHelperService = theIdHelperService;
067        }
068
069        /**
070         * Expands a Group resource and returns the Group members' resource persistent ids.
071         * The returned ids consists of group members + all MDM matched resources based on EID only.
072         *
073         * <p>This method:</p>
074         * <ol>
075         *   <li>Reads the specified Group resource</li>
076         *   <li>Extracts all member entity references from the Group</li>
077         *   <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>
078         *   <li>Converts the expanded resource IDs to persistent IDs (PIDs)</li>
079         * </ol>
080         *
081         * @param groupResourceId The ID of the Group resource to expand
082         * @param requestPartitionId The request partition ID
083         * @return A set of {@link JpaPid} objects representing all expanded resources
084         */
085        @Override
086        public Set<JpaPid> expandGroup(String groupResourceId, RequestPartitionId requestPartitionId) {
087                // Read the Group resource
088                SystemRequestDetails srd = SystemRequestDetails.forRequestPartitionId(requestPartitionId);
089                IIdType groupId = myFhirContext.getVersion().newIdType(groupResourceId);
090                IFhirResourceDao<?> groupDao = myDaoRegistry.getResourceDao("Group");
091                IBaseResource groupResource = groupDao.read(groupId, srd);
092
093                Set<String> allResourceIds = new HashSet<>();
094                FhirTerser terser = myFhirContext.newTerser();
095                // Extract all member.entity references from the Group resource
096                List<IBaseReference> memberEntities =
097                                terser.getValues(groupResource, "Group.member.entity", IBaseReference.class);
098                // mdm expand each member based on eid
099                for (IBaseReference entityRef : memberEntities) {
100                        if (!entityRef.getReferenceElement().isEmpty()) {
101                                IIdType memberId = entityRef.getReferenceElement();
102                                Set<String> expanded =
103                                                myMdmEidMatchOnlyLinkExpandSvc.expandMdmBySourceResourceId(requestPartitionId, memberId);
104                                allResourceIds.addAll(expanded);
105                        }
106                }
107                // Convert all resourceIds to IIdType and resolve in batch
108                List<IIdType> idTypes = allResourceIds.stream()
109                                .map(id -> myFhirContext.getVersion().newIdType(id))
110                                .collect(Collectors.toList());
111                List<JpaPid> pidList = myIdHelperService.resolveResourcePids(
112                                requestPartitionId,
113                                idTypes,
114                                ResolveIdentityMode.excludeDeleted().cacheOk());
115                return new HashSet<>(pidList);
116        }
117
118        @Override
119        public void annotateResource(IBaseResource resource) {
120                // This function is normally used to add golden resource id to the exported resources,
121                // but in the Eid-based match only mode, there isn't any golden resource, so nothing to do here
122        }
123}