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}