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}