001/*-
002 * #%L
003 * HAPI FHIR - Master Data Management
004 * %%
005 * Copyright (C) 2014 - 2024 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.jpa.api.dao.DaoRegistry;
024import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
025import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
026import ca.uhn.fhir.mdm.api.IMdmLinkQuerySvc;
027import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService;
028import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
029import ca.uhn.fhir.mdm.api.paging.MdmPageRequest;
030import ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters;
031import ca.uhn.fhir.mdm.model.MdmTransactionContext;
032import ca.uhn.fhir.mdm.model.mdmevents.MdmLinkJson;
033import ca.uhn.fhir.mdm.util.GoldenResourceHelper;
034import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
035import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
036import ca.uhn.fhir.util.TerserUtil;
037import org.hl7.fhir.instance.model.api.IAnyResource;
038import org.hl7.fhir.instance.model.api.IBase;
039import org.hl7.fhir.instance.model.api.IBaseResource;
040import org.springframework.data.domain.Page;
041
042import java.util.stream.Stream;
043
044public class MdmSurvivorshipSvcImpl implements IMdmSurvivorshipService {
045
046        protected final FhirContext myFhirContext;
047
048        private final GoldenResourceHelper myGoldenResourceHelper;
049
050        private final DaoRegistry myDaoRegistry;
051        private final IMdmLinkQuerySvc myMdmLinkQuerySvc;
052
053        private final IIdHelperService<?> myIIdHelperService;
054
055        public MdmSurvivorshipSvcImpl(
056                        FhirContext theFhirContext,
057                        GoldenResourceHelper theResourceHelper,
058                        DaoRegistry theDaoRegistry,
059                        IMdmLinkQuerySvc theLinkQuerySvc,
060                        IIdHelperService<?> theIIdHelperService) {
061                myFhirContext = theFhirContext;
062                myGoldenResourceHelper = theResourceHelper;
063                myDaoRegistry = theDaoRegistry;
064                myMdmLinkQuerySvc = theLinkQuerySvc;
065                myIIdHelperService = theIIdHelperService;
066        }
067
068        // this logic is custom in smile vs hapi
069        @Override
070        public <T extends IBase> void applySurvivorshipRulesToGoldenResource(
071                        T theTargetResource, T theGoldenResource, MdmTransactionContext theMdmTransactionContext) {
072                switch (theMdmTransactionContext.getRestOperation()) {
073                        case MERGE_GOLDEN_RESOURCES:
074                                TerserUtil.mergeFields(
075                                                myFhirContext,
076                                                (IBaseResource) theTargetResource,
077                                                (IBaseResource) theGoldenResource,
078                                                TerserUtil.EXCLUDE_IDS_AND_META);
079                                break;
080                        default:
081                                TerserUtil.replaceFields(
082                                                myFhirContext,
083                                                (IBaseResource) theTargetResource,
084                                                (IBaseResource) theGoldenResource,
085                                                TerserUtil.EXCLUDE_IDS_AND_META);
086                                break;
087                }
088        }
089
090        // This logic is the same for all implementations (including jpa or mongo)
091        @SuppressWarnings({"rawtypes", "unchecked"})
092        @Override
093        public <T extends IBase> T rebuildGoldenResourceWithSurvivorshipRules(
094                        T theGoldenResourceBase, MdmTransactionContext theMdmTransactionContext) {
095                IBaseResource goldenResource = (IBaseResource) theGoldenResourceBase;
096
097                // we want a list of source ids linked to this
098                // golden resource id; sorted and filtered for only MATCH results
099                Stream<IBaseResource> sourceResources =
100                                getMatchedSourceIdsByLinkUpdateDate(goldenResource, theMdmTransactionContext);
101
102                IBaseResource toSave = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(
103                                (IAnyResource) goldenResource,
104                                theMdmTransactionContext,
105                                null // we don't want to apply survivorship - just create a new GoldenResource
106                                );
107
108                toSave.setId(goldenResource.getIdElement().toUnqualifiedVersionless());
109
110                sourceResources.forEach(source -> {
111                        applySurvivorshipRulesToGoldenResource(source, toSave, theMdmTransactionContext);
112                });
113
114                // save it
115                IFhirResourceDao dao = myDaoRegistry.getResourceDao(goldenResource.fhirType());
116                dao.update(toSave, new SystemRequestDetails());
117
118                return (T) toSave;
119        }
120
121        private Stream<IBaseResource> getMatchedSourceIdsByLinkUpdateDate(
122                        IBaseResource theGoldenResource, MdmTransactionContext theMdmTransactionContext) {
123                String resourceType = theGoldenResource.fhirType();
124                IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(resourceType);
125
126                MdmQuerySearchParameters searchParameters = new MdmQuerySearchParameters(new MdmPageRequest(0, 50, 50, 50));
127                searchParameters.setGoldenResourceId(theGoldenResource.getIdElement());
128                searchParameters.setSort("myUpdated");
129                searchParameters.setMatchResult(MdmMatchResultEnum.MATCH);
130                Page<MdmLinkJson> linksQuery = myMdmLinkQuerySvc.queryLinks(searchParameters, theMdmTransactionContext);
131
132                return linksQuery.get().map(link -> {
133                        String sourceId = link.getSourceId();
134
135                        // +1 because of "/" in id: "ResourceType/Id"
136                        IResourcePersistentId<?> pid = getResourcePID(sourceId.substring(resourceType.length() + 1), resourceType);
137
138                        // this might be a bit unperformant
139                        // but it depends how many links there are
140                        // per golden resource (unlikely to be thousands)
141                        return dao.readByPid(pid);
142                });
143        }
144
145        private IResourcePersistentId<?> getResourcePID(String theId, String theResourceType) {
146                return myIIdHelperService.newPidFromStringIdAndResourceName(theId, theResourceType);
147        }
148}