001package ca.uhn.fhir.mdm.svc;
002
003/*-
004 * #%L
005 * HAPI FHIR - Master Data Management
006 * %%
007 * Copyright (C) 2014 - 2023 Smile CDR, Inc.
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.i18n.Msg;
024import ca.uhn.fhir.interceptor.model.RequestPartitionId;
025import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
026import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
027import ca.uhn.fhir.jpa.dao.IResultIterator;
028import ca.uhn.fhir.jpa.dao.ISearchBuilder;
029import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
030import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
031import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
032import ca.uhn.fhir.mdm.api.IMdmChannelSubmitterSvc;
033import ca.uhn.fhir.mdm.api.IMdmSettings;
034import ca.uhn.fhir.mdm.api.IMdmSubmitSvc;
035import ca.uhn.fhir.mdm.log.Logs;
036import ca.uhn.fhir.rest.api.server.RequestDetails;
037import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
038import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
039import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
040import ca.uhn.fhir.rest.server.provider.ProviderConstants;
041import org.hl7.fhir.instance.model.api.IBaseResource;
042import org.hl7.fhir.instance.model.api.IIdType;
043import org.slf4j.Logger;
044import org.springframework.beans.factory.annotation.Autowired;
045import org.springframework.transaction.annotation.Transactional;
046
047import javax.annotation.Nonnull;
048import javax.annotation.Nullable;
049import java.io.IOException;
050import java.util.ArrayList;
051import java.util.Collection;
052import java.util.Collections;
053import java.util.List;
054import java.util.UUID;
055
056public class MdmSubmitSvcImpl implements IMdmSubmitSvc {
057
058        private static final Logger ourLog = Logs.getMdmTroubleshootingLog();
059
060        @Autowired
061        private DaoRegistry myDaoRegistry;
062
063        @Autowired
064        private MdmSearchParamSvc myMdmSearchParamSvc;
065
066        @Autowired
067        private IMdmChannelSubmitterSvc myMdmChannelSubmitterSvc;
068
069        @Autowired
070        private IMdmSettings myMdmSettings;
071
072        @Autowired
073        private IRequestPartitionHelperSvc myRequestPartitionHelperSvc;
074
075        public static final int DEFAULT_BUFFER_SIZE = 100;
076
077        private int myBufferSize = DEFAULT_BUFFER_SIZE;
078
079        public MdmSubmitSvcImpl() {
080        }
081
082        @Override
083        @Transactional
084        public long submitAllSourceTypesToMdm(@Nullable String theCriteria, @Nonnull RequestDetails theRequestDetails) {
085                long submittedCount = myMdmSettings.getMdmRules().getMdmTypes().stream()
086                        .mapToLong(type -> submitSourceResourceTypeToMdm(type, theCriteria, theRequestDetails))
087                        .sum();
088
089                return submittedCount;
090        }
091
092        @Override
093        @Transactional
094        public long submitSourceResourceTypeToMdm(String theSourceResourceType, @Nullable String theCriteria, @Nonnull RequestDetails theRequestDetails) {
095                if (theCriteria == null) {
096                        ourLog.info("Submitting all resources of type {} to MDM", theSourceResourceType);
097                } else {
098                        ourLog.info("Submitting resources of type {} with criteria {} to MDM", theSourceResourceType, theCriteria);
099                }
100
101                validateSourceType(theSourceResourceType);
102                SearchParameterMap spMap = myMdmSearchParamSvc.getSearchParameterMapFromCriteria(theSourceResourceType, theCriteria);
103                spMap.setLoadSynchronous(true);
104                spMap.setCount(myBufferSize);
105                ISearchBuilder searchBuilder = myMdmSearchParamSvc.generateSearchBuilderForType(theSourceResourceType);
106
107                RequestPartitionId requestPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequestDetails, theSourceResourceType, spMap, null);
108                return submitAllMatchingResourcesToMdmChannel(spMap, searchBuilder, requestPartitionId);
109        }
110
111        private long submitAllMatchingResourcesToMdmChannel(SearchParameterMap theSpMap, ISearchBuilder theSearchBuilder, RequestPartitionId theRequestPartitionId) {
112                SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(null, UUID.randomUUID().toString());
113                long total = 0;
114                try (IResultIterator query = theSearchBuilder.createQuery(theSpMap, searchRuntimeDetails, null, theRequestPartitionId)) {
115                        Collection<IResourcePersistentId> pidBatch;
116                        do {
117                                pidBatch = query.getNextResultBatch(myBufferSize);
118                                total += loadPidsAndSubmitToMdmChannel(theSearchBuilder, pidBatch);
119                        } while (query.hasNext());
120                } catch (IOException theE) {
121                        throw new InternalErrorException(Msg.code(749) + "Failure while attempting to query resources for " + ProviderConstants.OPERATION_MDM_SUBMIT, theE);
122                }
123                ourLog.info("MDM Submit complete.  Submitted a total of {} resources.", total);
124                return total;
125        }
126
127        /**
128         * Given a collection of IResourcePersistentId objects, and a search builder, load the IBaseResources and submit them to
129         * the MDM channel for processing.
130         *
131         * @param theSearchBuilder the related DAO search builder.
132         * @param thePidsToSubmit The collection of PIDs whos resources you want to submit for MDM processing.
133         *
134         * @return The total count of submitted resources.
135         */
136        private long loadPidsAndSubmitToMdmChannel(ISearchBuilder theSearchBuilder, Collection<IResourcePersistentId> thePidsToSubmit) {
137                List<IBaseResource> resourcesToSubmit = new ArrayList<>();
138                theSearchBuilder.loadResourcesByPid(thePidsToSubmit, Collections.emptyList(), resourcesToSubmit, false, null);
139                ourLog.info("Submitting {} resources to MDM", resourcesToSubmit.size());
140                resourcesToSubmit
141                        .forEach(resource -> myMdmChannelSubmitterSvc.submitResourceToMdmChannel(resource));
142                return resourcesToSubmit.size();
143        }
144
145        @Override
146        @Transactional
147        public long submitPractitionerTypeToMdm(@Nullable String theCriteria, @Nonnull RequestDetails theRequestDetails) {
148                return submitSourceResourceTypeToMdm("Practitioner", theCriteria, theRequestDetails);
149        }
150
151        @Override
152        @Transactional
153        public long submitPatientTypeToMdm(@Nullable String theCriteria, @Nonnull RequestDetails theRequestDetails) {
154                return submitSourceResourceTypeToMdm("Patient", theCriteria, theRequestDetails);
155        }
156
157        @Override
158        @Transactional
159        public long submitSourceResourceToMdm(IIdType theId, RequestDetails theRequestDetails) {
160                validateSourceType(theId.getResourceType());
161                IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theId.getResourceType());
162                IBaseResource read = resourceDao.read(theId, theRequestDetails);
163                myMdmChannelSubmitterSvc.submitResourceToMdmChannel(read);
164                return 1;
165        }
166
167        @Override
168        public void setMdmSettings(IMdmSettings theMdmSettings) {
169                myMdmSettings = theMdmSettings;
170        }
171
172        private void validateSourceType(String theResourceType) {
173                if(!myMdmSettings.getMdmRules().getMdmTypes().contains(theResourceType)) {
174                        throw new InvalidRequestException(Msg.code(750) + ProviderConstants.OPERATION_MDM_SUBMIT + " does not support resource type: " + theResourceType);
175                }
176        }
177
178        @Override
179        public void setBufferSize(int myBufferSize) {
180                this.myBufferSize = myBufferSize;
181        }
182}