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.i18n.Msg; 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.dao.IResultIterator; 027import ca.uhn.fhir.jpa.dao.ISearchBuilder; 028import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; 029import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc; 030import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 031import ca.uhn.fhir.mdm.api.IMdmChannelSubmitterSvc; 032import ca.uhn.fhir.mdm.api.IMdmSettings; 033import ca.uhn.fhir.mdm.api.IMdmSubmitSvc; 034import ca.uhn.fhir.mdm.log.Logs; 035import ca.uhn.fhir.rest.api.server.RequestDetails; 036import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId; 037import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 038import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 039import ca.uhn.fhir.rest.server.provider.ProviderConstants; 040import jakarta.annotation.Nonnull; 041import jakarta.annotation.Nullable; 042import org.hl7.fhir.instance.model.api.IBaseResource; 043import org.hl7.fhir.instance.model.api.IIdType; 044import org.slf4j.Logger; 045import org.springframework.beans.factory.annotation.Autowired; 046import org.springframework.transaction.annotation.Transactional; 047 048import java.io.IOException; 049import java.util.ArrayList; 050import java.util.Collection; 051import java.util.Collections; 052import java.util.List; 053import java.util.UUID; 054 055public class MdmSubmitSvcImpl implements IMdmSubmitSvc { 056 057 private static final Logger ourLog = Logs.getMdmTroubleshootingLog(); 058 059 @Autowired 060 private DaoRegistry myDaoRegistry; 061 062 @Autowired 063 private MdmSearchParamSvc myMdmSearchParamSvc; 064 065 @Autowired 066 private IMdmChannelSubmitterSvc myMdmChannelSubmitterSvc; 067 068 @Autowired 069 private IMdmSettings myMdmSettings; 070 071 @Autowired 072 private IRequestPartitionHelperSvc myRequestPartitionHelperSvc; 073 074 public static final int DEFAULT_BUFFER_SIZE = 100; 075 076 private int myBufferSize = DEFAULT_BUFFER_SIZE; 077 078 public MdmSubmitSvcImpl() {} 079 080 @Override 081 @Transactional 082 public long submitAllSourceTypesToMdm(@Nullable String theCriteria, @Nonnull RequestDetails theRequestDetails) { 083 long submittedCount = myMdmSettings.getMdmRules().getMdmTypes().stream() 084 .mapToLong(type -> submitSourceResourceTypeToMdm(type, theCriteria, theRequestDetails)) 085 .sum(); 086 087 return submittedCount; 088 } 089 090 @Override 091 @Transactional 092 public long submitSourceResourceTypeToMdm( 093 String theSourceResourceType, @Nullable String theCriteria, @Nonnull RequestDetails theRequestDetails) { 094 if (theCriteria == null) { 095 ourLog.info("Submitting all resources of type {} to MDM", theSourceResourceType); 096 } else { 097 ourLog.info("Submitting resources of type {} with criteria {} to MDM", theSourceResourceType, theCriteria); 098 } 099 100 validateSourceType(theSourceResourceType); 101 SearchParameterMap spMap = 102 myMdmSearchParamSvc.getSearchParameterMapFromCriteria(theSourceResourceType, theCriteria); 103 spMap.setLoadSynchronous(true); 104 spMap.setCount(myBufferSize); 105 ISearchBuilder searchBuilder = myMdmSearchParamSvc.generateSearchBuilderForType(theSourceResourceType); 106 107 RequestPartitionId requestPartitionId = 108 myRequestPartitionHelperSvc.determineReadPartitionForRequestForSearchType( 109 theRequestDetails, theSourceResourceType, spMap); 110 return submitAllMatchingResourcesToMdmChannel(spMap, searchBuilder, requestPartitionId); 111 } 112 113 private long submitAllMatchingResourcesToMdmChannel( 114 SearchParameterMap theSpMap, ISearchBuilder theSearchBuilder, RequestPartitionId theRequestPartitionId) { 115 SearchRuntimeDetails searchRuntimeDetails = 116 new SearchRuntimeDetails(null, UUID.randomUUID().toString()); 117 long total = 0; 118 try (IResultIterator query = 119 theSearchBuilder.createQuery(theSpMap, searchRuntimeDetails, null, theRequestPartitionId)) { 120 Collection<IResourcePersistentId> pidBatch; 121 do { 122 pidBatch = query.getNextResultBatch(myBufferSize); 123 total += loadPidsAndSubmitToMdmChannel(theSearchBuilder, pidBatch); 124 } while (query.hasNext()); 125 } catch (IOException theE) { 126 throw new InternalErrorException( 127 Msg.code(749) + "Failure while attempting to query resources for " 128 + ProviderConstants.OPERATION_MDM_SUBMIT, 129 theE); 130 } 131 ourLog.info("MDM Submit complete. Submitted a total of {} resources.", total); 132 return total; 133 } 134 135 /** 136 * Given a collection of IResourcePersistentId objects, and a search builder, load the IBaseResources and submit them to 137 * the MDM channel for processing. 138 * 139 * @param theSearchBuilder the related DAO search builder. 140 * @param thePidsToSubmit The collection of PIDs whos resources you want to submit for MDM processing. 141 * 142 * @return The total count of submitted resources. 143 */ 144 private long loadPidsAndSubmitToMdmChannel( 145 ISearchBuilder theSearchBuilder, Collection<IResourcePersistentId> thePidsToSubmit) { 146 List<IBaseResource> resourcesToSubmit = new ArrayList<>(); 147 theSearchBuilder.loadResourcesByPid(thePidsToSubmit, Collections.emptyList(), resourcesToSubmit, false, null); 148 ourLog.info("Submitting {} resources to MDM", resourcesToSubmit.size()); 149 resourcesToSubmit.forEach(resource -> myMdmChannelSubmitterSvc.submitResourceToMdmChannel(resource)); 150 return resourcesToSubmit.size(); 151 } 152 153 @Override 154 @Transactional 155 public long submitPractitionerTypeToMdm(@Nullable String theCriteria, @Nonnull RequestDetails theRequestDetails) { 156 return submitSourceResourceTypeToMdm("Practitioner", theCriteria, theRequestDetails); 157 } 158 159 @Override 160 @Transactional 161 public long submitPatientTypeToMdm(@Nullable String theCriteria, @Nonnull RequestDetails theRequestDetails) { 162 return submitSourceResourceTypeToMdm("Patient", theCriteria, theRequestDetails); 163 } 164 165 @Override 166 @Transactional 167 public long submitSourceResourceToMdm(IIdType theId, RequestDetails theRequestDetails) { 168 validateSourceType(theId.getResourceType()); 169 IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theId.getResourceType()); 170 IBaseResource read = resourceDao.read(theId, theRequestDetails); 171 myMdmChannelSubmitterSvc.submitResourceToMdmChannel(read); 172 return 1; 173 } 174 175 @Override 176 public void setMdmSettings(IMdmSettings theMdmSettings) { 177 myMdmSettings = theMdmSettings; 178 } 179 180 private void validateSourceType(String theResourceType) { 181 if (!myMdmSettings.getMdmRules().getMdmTypes().contains(theResourceType)) { 182 throw new InvalidRequestException(Msg.code(750) + ProviderConstants.OPERATION_MDM_SUBMIT 183 + " does not support resource type: " + theResourceType); 184 } 185 } 186 187 @Override 188 public void setBufferSize(int myBufferSize) { 189 this.myBufferSize = myBufferSize; 190 } 191}