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.provider;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.i18n.Msg;
024import ca.uhn.fhir.interceptor.api.HookParams;
025import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
026import ca.uhn.fhir.interceptor.api.Pointcut;
027import ca.uhn.fhir.mdm.api.IMdmControllerSvc;
028import ca.uhn.fhir.mdm.api.IMdmSettings;
029import ca.uhn.fhir.mdm.api.IMdmSubmitSvc;
030import ca.uhn.fhir.mdm.api.MdmConstants;
031import ca.uhn.fhir.mdm.api.paging.MdmPageRequest;
032import ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters;
033import ca.uhn.fhir.mdm.model.MdmCreateOrUpdateParams;
034import ca.uhn.fhir.mdm.model.MdmMergeGoldenResourcesParams;
035import ca.uhn.fhir.mdm.model.MdmTransactionContext;
036import ca.uhn.fhir.mdm.model.MdmUnduplicateGoldenResourceParams;
037import ca.uhn.fhir.mdm.model.mdmevents.MdmLinkJson;
038import ca.uhn.fhir.mdm.model.mdmevents.MdmSubmitEvent;
039import ca.uhn.fhir.model.api.annotation.Description;
040import ca.uhn.fhir.rest.annotation.IdParam;
041import ca.uhn.fhir.rest.annotation.Operation;
042import ca.uhn.fhir.rest.annotation.OperationParam;
043import ca.uhn.fhir.rest.api.Constants;
044import ca.uhn.fhir.rest.api.server.RequestDetails;
045import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
046import ca.uhn.fhir.rest.server.provider.ProviderConstants;
047import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
048import ca.uhn.fhir.util.ParametersUtil;
049import jakarta.annotation.Nonnull;
050import org.apache.commons.lang3.StringUtils;
051import org.hl7.fhir.instance.model.api.IAnyResource;
052import org.hl7.fhir.instance.model.api.IBaseBundle;
053import org.hl7.fhir.instance.model.api.IBaseParameters;
054import org.hl7.fhir.instance.model.api.IBaseResource;
055import org.hl7.fhir.instance.model.api.IIdType;
056import org.hl7.fhir.instance.model.api.IPrimitiveType;
057import org.slf4j.Logger;
058import org.springframework.data.domain.Page;
059
060import java.math.BigDecimal;
061import java.util.ArrayList;
062import java.util.Collections;
063import java.util.List;
064import java.util.stream.Collectors;
065
066import static ca.uhn.fhir.rest.api.Constants.PARAM_OFFSET;
067import static org.apache.commons.lang3.ObjectUtils.isNotEmpty;
068import static org.apache.commons.lang3.StringUtils.isNotBlank;
069import static org.slf4j.LoggerFactory.getLogger;
070
071public class MdmProviderDstu3Plus extends BaseMdmProvider {
072        private static final Logger ourLog = getLogger(MdmProviderDstu3Plus.class);
073
074        private static final String PATIENT_RESOURCE = "Patient";
075        private static final String PRACTITIONER_RESOURCE = "Practitioner";
076
077        private final IMdmSubmitSvc myMdmSubmitSvc;
078        private final IMdmSettings myMdmSettings;
079        private final MdmControllerHelper myMdmControllerHelper;
080
081        private final IInterceptorBroadcaster myInterceptorBroadcaster;
082
083        public static final int DEFAULT_PAGE_SIZE = 20;
084        public static final int MAX_PAGE_SIZE = 100;
085
086        /**
087         * Constructor
088         * <p>
089         * Note that this is not a spring bean. Any necessary injections should
090         * happen in the constructor
091         */
092        public MdmProviderDstu3Plus(
093                        FhirContext theFhirContext,
094                        IMdmControllerSvc theMdmControllerSvc,
095                        MdmControllerHelper theMdmHelper,
096                        IMdmSubmitSvc theMdmSubmitSvc,
097                        IInterceptorBroadcaster theIInterceptorBroadcaster,
098                        IMdmSettings theIMdmSettings) {
099                super(theFhirContext, theMdmControllerSvc);
100                myMdmControllerHelper = theMdmHelper;
101                myMdmSubmitSvc = theMdmSubmitSvc;
102                myInterceptorBroadcaster = theIInterceptorBroadcaster;
103                myMdmSettings = theIMdmSettings;
104        }
105
106        /**
107         * Searches for matches for hte provided resource.
108         *
109         * @param theResource - the resource to match on
110         * @param theResourceType - the resource type
111         * @param theRequestDetails - the request details
112         * @return - any matches to the provided resource
113         */
114        @Operation(name = ProviderConstants.MDM_MATCH)
115        public IBaseBundle serverMatch(
116                        @OperationParam(name = ProviderConstants.MDM_MATCH_RESOURCE, min = 1, max = 1) IAnyResource theResource,
117                        @OperationParam(name = ProviderConstants.MDM_RESOURCE_TYPE, min = 1, max = 1, typeName = "string")
118                                        IPrimitiveType<String> theResourceType,
119                        RequestDetails theRequestDetails) {
120                if (theResource == null) {
121                        throw new InvalidRequestException(Msg.code(1499) + "resource may not be null");
122                }
123                return myMdmControllerHelper.getMatchesAndPossibleMatchesForResource(
124                                theResource, theResourceType.getValueAsString(), theRequestDetails);
125        }
126
127        @Operation(name = ProviderConstants.MDM_MERGE_GOLDEN_RESOURCES)
128        public IBaseResource mergeGoldenResources(
129                        @OperationParam(
130                                                        name = ProviderConstants.MDM_MERGE_GR_FROM_GOLDEN_RESOURCE_ID,
131                                                        min = 1,
132                                                        max = 1,
133                                                        typeName = "string")
134                                        IPrimitiveType<String> theFromGoldenResourceId,
135                        @OperationParam(
136                                                        name = ProviderConstants.MDM_MERGE_GR_TO_GOLDEN_RESOURCE_ID,
137                                                        min = 1,
138                                                        max = 1,
139                                                        typeName = "string")
140                                        IPrimitiveType<String> theToGoldenResourceId,
141                        @OperationParam(name = ProviderConstants.MDM_MERGE_RESOURCE, max = 1) IAnyResource theMergedResource,
142                        RequestDetails theRequestDetails) {
143                validateMergeParameters(theFromGoldenResourceId, theToGoldenResourceId);
144
145                MdmTransactionContext.OperationType operationType = (theMergedResource == null)
146                                ? MdmTransactionContext.OperationType.MERGE_GOLDEN_RESOURCES
147                                : MdmTransactionContext.OperationType.MANUAL_MERGE_GOLDEN_RESOURCES;
148                MdmTransactionContext txContext = createMdmContext(
149                                theRequestDetails,
150                                operationType,
151                                getResourceType(ProviderConstants.MDM_MERGE_GR_FROM_GOLDEN_RESOURCE_ID, theFromGoldenResourceId));
152
153                MdmMergeGoldenResourcesParams params = new MdmMergeGoldenResourcesParams();
154                params.setFromGoldenResourceId(theFromGoldenResourceId.getValueAsString());
155                params.setToGoldenResourceId(theToGoldenResourceId.getValueAsString());
156                params.setManuallyMergedResource(theMergedResource);
157                params.setMdmTransactionContext(txContext);
158                params.setRequestDetails(theRequestDetails);
159
160                return myMdmControllerSvc.mergeGoldenResources(params);
161        }
162
163        @Operation(name = ProviderConstants.MDM_UPDATE_LINK)
164        public IBaseResource updateLink(
165                        @OperationParam(name = ProviderConstants.MDM_UPDATE_LINK_GOLDEN_RESOURCE_ID, min = 1, max = 1)
166                                        IPrimitiveType<String> theGoldenResourceId,
167                        @OperationParam(name = ProviderConstants.MDM_UPDATE_LINK_RESOURCE_ID, min = 1, max = 1)
168                                        IPrimitiveType<String> theResourceId,
169                        @OperationParam(name = ProviderConstants.MDM_UPDATE_LINK_MATCH_RESULT, min = 1, max = 1)
170                                        IPrimitiveType<String> theMatchResult,
171                        ServletRequestDetails theRequestDetails) {
172                validateUpdateLinkParameters(theGoldenResourceId, theResourceId, theMatchResult);
173
174                MdmCreateOrUpdateParams updateLinkParams = new MdmCreateOrUpdateParams();
175                updateLinkParams.setGoldenResourceId(theGoldenResourceId.getValueAsString());
176                updateLinkParams.setResourceId(theResourceId.getValue());
177                updateLinkParams.setMatchResult(MdmControllerUtil.extractMatchResultOrNull(theMatchResult.getValue()));
178                updateLinkParams.setMdmContext(createMdmContext(
179                                theRequestDetails,
180                                MdmTransactionContext.OperationType.UPDATE_LINK,
181                                getResourceType(ProviderConstants.MDM_UPDATE_LINK_GOLDEN_RESOURCE_ID, theGoldenResourceId)));
182                updateLinkParams.setRequestDetails(theRequestDetails);
183
184                return myMdmControllerSvc.updateLink(updateLinkParams);
185        }
186
187        @Operation(name = ProviderConstants.MDM_CREATE_LINK)
188        public IBaseResource createLink(
189                        @OperationParam(name = ProviderConstants.MDM_CREATE_LINK_GOLDEN_RESOURCE_ID, min = 1, max = 1)
190                                        IPrimitiveType<String> theGoldenResourceId,
191                        @OperationParam(name = ProviderConstants.MDM_CREATE_LINK_RESOURCE_ID, min = 1, max = 1)
192                                        IPrimitiveType<String> theResourceId,
193                        @OperationParam(name = ProviderConstants.MDM_CREATE_LINK_MATCH_RESULT, min = 0, max = 1)
194                                        IPrimitiveType<String> theMatchResult,
195                        ServletRequestDetails theRequestDetails) {
196                validateCreateLinkParameters(theGoldenResourceId, theResourceId, theMatchResult);
197
198                MdmCreateOrUpdateParams params = new MdmCreateOrUpdateParams();
199                params.setGoldenResourceId(theGoldenResourceId.getValueAsString());
200                params.setResourceId(theResourceId.getValue());
201                params.setMatchResult(MdmControllerUtil.extractMatchResultOrNull(extractStringOrNull(theMatchResult)));
202                params.setRequestDetails(theRequestDetails);
203                params.setMdmContext(createMdmContext(
204                                theRequestDetails,
205                                MdmTransactionContext.OperationType.CREATE_LINK,
206                                getResourceType(ProviderConstants.MDM_CREATE_LINK_GOLDEN_RESOURCE_ID, theGoldenResourceId)));
207
208                return myMdmControllerSvc.createLink(params);
209        }
210
211        @Operation(
212                        name = ProviderConstants.OPERATION_MDM_CLEAR,
213                        returnParameters = {
214                                @OperationParam(name = ProviderConstants.OPERATION_BATCH_RESPONSE_JOB_ID, typeName = "decimal")
215                        })
216        public IBaseParameters clearMdmLinks(
217                        @OperationParam(
218                                                        name = ProviderConstants.OPERATION_MDM_CLEAR_RESOURCE_NAME,
219                                                        min = 0,
220                                                        max = OperationParam.MAX_UNLIMITED,
221                                                        typeName = "string")
222                                        List<IPrimitiveType<String>> theResourceNames,
223                        @OperationParam(name = ProviderConstants.OPERATION_MDM_BATCH_SIZE, typeName = "decimal", min = 0, max = 1)
224                                        IPrimitiveType<BigDecimal> theBatchSize,
225                        ServletRequestDetails theRequestDetails) {
226
227                List<String> resourceNames = new ArrayList<>();
228
229                if (isNotEmpty(theResourceNames)) {
230                        resourceNames.addAll(
231                                        theResourceNames.stream().map(IPrimitiveType::getValue).collect(Collectors.toList()));
232                        validateResourceNames(resourceNames);
233                } else {
234                        resourceNames.addAll(myMdmSettings.getMdmRules().getMdmTypes());
235                }
236
237                return myMdmControllerSvc.submitMdmClearJob(resourceNames, theBatchSize, theRequestDetails);
238        }
239
240        private void validateResourceNames(List<String> theResourceNames) {
241                for (String resourceName : theResourceNames) {
242                        if (!myMdmSettings.isSupportedMdmType(resourceName)) {
243                                throw new InvalidRequestException(Msg.code(1500) + ProviderConstants.OPERATION_MDM_CLEAR
244                                                + " does not support resource type: " + resourceName);
245                        }
246                }
247        }
248
249        // Is a set of the OR sufficient ot the contenxt she's investigating?
250        @Operation(name = ProviderConstants.MDM_QUERY_LINKS, idempotent = true)
251        public IBaseParameters queryLinks(
252                        @OperationParam(
253                                                        name = ProviderConstants.MDM_QUERY_LINKS_GOLDEN_RESOURCE_ID,
254                                                        min = 0,
255                                                        max = 1,
256                                                        typeName = "string")
257                                        IPrimitiveType<String> theGoldenResourceId,
258                        @OperationParam(name = ProviderConstants.MDM_QUERY_LINKS_RESOURCE_ID, min = 0, max = 1, typeName = "string")
259                                        IPrimitiveType<String> theResourceId,
260                        @OperationParam(
261                                                        name = ProviderConstants.MDM_QUERY_LINKS_MATCH_RESULT,
262                                                        min = 0,
263                                                        max = 1,
264                                                        typeName = "string")
265                                        IPrimitiveType<String> theMatchResult,
266                        @OperationParam(name = ProviderConstants.MDM_QUERY_LINKS_LINK_SOURCE, min = 0, max = 1, typeName = "string")
267                                        IPrimitiveType<String> theLinkSource,
268                        @Description(
269                                                        value =
270                                                                        "Results from this method are returned across multiple pages. This parameter controls the offset when fetching a page.")
271                                        @OperationParam(name = PARAM_OFFSET, min = 0, max = 1, typeName = "integer")
272                                        IPrimitiveType<Integer> theOffset,
273                        @Description(
274                                                        value =
275                                                                        "Results from this method are returned across multiple pages. This parameter controls the size of those pages.")
276                                        @OperationParam(name = Constants.PARAM_COUNT, min = 0, max = 1, typeName = "integer")
277                                        IPrimitiveType<Integer> theCount,
278                        @OperationParam(name = Constants.PARAM_SORT, min = 0, max = 1, typeName = "string")
279                                        IPrimitiveType<String> theSort,
280                        ServletRequestDetails theRequestDetails,
281                        @OperationParam(name = ProviderConstants.MDM_RESOURCE_TYPE, min = 0, max = 1, typeName = "string")
282                                        IPrimitiveType<String> theResourceType) {
283                MdmPageRequest mdmPageRequest = new MdmPageRequest(theOffset, theCount, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE);
284                MdmTransactionContext mdmContext = createMdmContext(
285                                theRequestDetails,
286                                MdmTransactionContext.OperationType.QUERY_LINKS,
287                                getResourceType(
288                                                ProviderConstants.MDM_QUERY_LINKS_GOLDEN_RESOURCE_ID, theGoldenResourceId, theResourceType));
289                MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(mdmPageRequest)
290                                .setGoldenResourceId(extractStringOrNull(theGoldenResourceId))
291                                .setSourceId(extractStringOrNull(theResourceId))
292                                .setLinkSource(extractStringOrNull(theLinkSource))
293                                .setMatchResult(extractStringOrNull(theMatchResult))
294                                .setResourceType(extractStringOrNull(theResourceType))
295                                .setSort(extractStringOrNull(theSort));
296
297                Page<MdmLinkJson> mdmLinkJson =
298                                myMdmControllerSvc.queryLinks(mdmQuerySearchParameters, mdmContext, theRequestDetails);
299                return parametersFromMdmLinks(mdmLinkJson, true, theRequestDetails, mdmPageRequest);
300        }
301
302        @Operation(name = ProviderConstants.MDM_DUPLICATE_GOLDEN_RESOURCES, idempotent = true)
303        public IBaseParameters getDuplicateGoldenResources(
304                        @Description(
305                                                        formalDefinition =
306                                                                        "Results from this method are returned across multiple pages. This parameter controls the offset when fetching a page.")
307                                        @OperationParam(name = PARAM_OFFSET, min = 0, max = 1, typeName = "integer")
308                                        IPrimitiveType<Integer> theOffset,
309                        @Description(
310                                                        formalDefinition =
311                                                                        "Results from this method are returned across multiple pages. This parameter controls the size of those pages.")
312                                        @OperationParam(name = Constants.PARAM_COUNT, min = 0, max = 1, typeName = "integer")
313                                        IPrimitiveType<Integer> theCount,
314                        ServletRequestDetails theRequestDetails,
315                        @Description(formalDefinition = "This parameter controls the returned resource type.")
316                                        @OperationParam(name = ProviderConstants.MDM_RESOURCE_TYPE, min = 0, max = 1, typeName = "string")
317                                        IPrimitiveType<String> theResourceType) {
318
319                MdmPageRequest mdmPageRequest = new MdmPageRequest(theOffset, theCount, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE);
320
321                Page<MdmLinkJson> possibleDuplicates = myMdmControllerSvc.getDuplicateGoldenResources(
322                                createMdmContext(
323                                                theRequestDetails, MdmTransactionContext.OperationType.DUPLICATE_GOLDEN_RESOURCES, null),
324                                mdmPageRequest,
325                                theRequestDetails,
326                                extractStringOrNull(theResourceType));
327
328                return parametersFromMdmLinks(possibleDuplicates, false, theRequestDetails, mdmPageRequest);
329        }
330
331        @Operation(name = ProviderConstants.MDM_NOT_DUPLICATE)
332        public IBaseParameters notDuplicate(
333                        @OperationParam(
334                                                        name = ProviderConstants.MDM_QUERY_LINKS_GOLDEN_RESOURCE_ID,
335                                                        min = 1,
336                                                        max = 1,
337                                                        typeName = "string")
338                                        IPrimitiveType<String> theGoldenResourceId,
339                        @OperationParam(name = ProviderConstants.MDM_QUERY_LINKS_RESOURCE_ID, min = 1, max = 1, typeName = "string")
340                                        IPrimitiveType<String> theResourceId,
341                        ServletRequestDetails theRequestDetails) {
342                validateNotDuplicateParameters(theGoldenResourceId, theResourceId);
343
344                MdmUnduplicateGoldenResourceParams params = new MdmUnduplicateGoldenResourceParams();
345                params.setRequestDetails(theRequestDetails);
346                params.setGoldenResourceId(theGoldenResourceId.getValueAsString());
347                params.setTargetGoldenResourceId(theResourceId.getValueAsString());
348                params.setMdmContext(createMdmContext(
349                                theRequestDetails,
350                                MdmTransactionContext.OperationType.NOT_DUPLICATE,
351                                getResourceType(ProviderConstants.MDM_QUERY_LINKS_GOLDEN_RESOURCE_ID, theGoldenResourceId)));
352
353                myMdmControllerSvc.unduplicateGoldenResource(params);
354
355                IBaseParameters retval = ParametersUtil.newInstance(myFhirContext);
356                ParametersUtil.addParameterToParametersBoolean(myFhirContext, retval, "success", true);
357                return retval;
358        }
359
360        @Operation(
361                        name = ProviderConstants.OPERATION_MDM_SUBMIT,
362                        idempotent = false,
363                        returnParameters = {
364                                @OperationParam(name = ProviderConstants.OPERATION_MDM_SUBMIT_OUT_PARAM_SUBMITTED, typeName = "integer")
365                        })
366        public IBaseParameters mdmBatchOnAllSourceResources(
367                        @OperationParam(name = ProviderConstants.MDM_BATCH_RUN_RESOURCE_TYPE, min = 0, max = 1, typeName = "string")
368                                        IPrimitiveType<String> theResourceType,
369                        @OperationParam(name = ProviderConstants.MDM_BATCH_RUN_CRITERIA, min = 0, max = 1, typeName = "string")
370                                        IPrimitiveType<String> theCriteria,
371                        @OperationParam(name = ProviderConstants.OPERATION_MDM_BATCH_SIZE, typeName = "decimal", min = 0, max = 1)
372                                        IPrimitiveType<BigDecimal> theBatchSize,
373                        ServletRequestDetails theRequestDetails) {
374                String criteria = convertStringTypeToString(theCriteria);
375                String resourceType = convertStringTypeToString(theResourceType);
376                long submittedCount;
377                IBaseParameters retval;
378                if (theRequestDetails.isPreferRespondAsync()) {
379                        List<String> urls = buildUrlsForJob(criteria, resourceType);
380                        retval = myMdmControllerSvc.submitMdmSubmitJob(urls, theBatchSize, theRequestDetails);
381                } else {
382                        submittedCount = synchronousMdmSubmit(resourceType, null, criteria, theRequestDetails);
383                        retval = buildMdmOutParametersWithCount(submittedCount);
384                }
385
386                return retval;
387        }
388
389        @Nonnull
390        private List<String> buildUrlsForJob(String criteria, String resourceType) {
391                List<String> urls = new ArrayList<>();
392                if (isNotBlank(resourceType)) {
393                        String theUrl = resourceType + "?" + criteria;
394                        urls.add(theUrl);
395                } else {
396                        myMdmSettings.getMdmRules().getMdmTypes().stream()
397                                        .map(type -> type + "?" + criteria)
398                                        .forEach(urls::add);
399                }
400                return urls;
401        }
402
403        private String convertStringTypeToString(IPrimitiveType<String> theCriteria) {
404                return theCriteria == null ? "" : theCriteria.getValueAsString();
405        }
406
407        @Operation(
408                        name = ProviderConstants.OPERATION_MDM_SUBMIT,
409                        idempotent = false,
410                        typeName = "Patient",
411                        returnParameters = {
412                                @OperationParam(name = ProviderConstants.OPERATION_BATCH_RESPONSE_JOB_ID, typeName = "integer")
413                        })
414        public IBaseParameters mdmBatchPatientInstance(@IdParam IIdType theIdParam, RequestDetails theRequest) {
415
416                long submittedCount = synchronousMdmSubmit(null, theIdParam, null, theRequest);
417                return buildMdmOutParametersWithCount(submittedCount);
418        }
419
420        @Operation(
421                        name = ProviderConstants.OPERATION_MDM_SUBMIT,
422                        idempotent = false,
423                        typeName = "Patient",
424                        returnParameters = {
425                                @OperationParam(name = ProviderConstants.OPERATION_BATCH_RESPONSE_JOB_ID, typeName = "integer")
426                        })
427        public IBaseParameters mdmBatchPatientType(
428                        @OperationParam(name = ProviderConstants.MDM_BATCH_RUN_CRITERIA, typeName = "string")
429                                        IPrimitiveType<String> theCriteria,
430                        @OperationParam(name = ProviderConstants.OPERATION_MDM_BATCH_SIZE, typeName = "decimal", min = 0, max = 1)
431                                        IPrimitiveType<BigDecimal> theBatchSize,
432                        ServletRequestDetails theRequest) {
433                if (theRequest.isPreferRespondAsync()) {
434                        String theUrl = PATIENT_RESOURCE + "?";
435                        return myMdmControllerSvc.submitMdmSubmitJob(Collections.singletonList(theUrl), theBatchSize, theRequest);
436                } else {
437                        String criteria = convertStringTypeToString(theCriteria);
438                        long submittedCount = synchronousMdmSubmit(PATIENT_RESOURCE, null, criteria, theRequest);
439                        return buildMdmOutParametersWithCount(submittedCount);
440                }
441        }
442
443        @Operation(
444                        name = ProviderConstants.OPERATION_MDM_SUBMIT,
445                        idempotent = false,
446                        typeName = "Practitioner",
447                        returnParameters = {
448                                @OperationParam(name = ProviderConstants.OPERATION_BATCH_RESPONSE_JOB_ID, typeName = "integer")
449                        })
450        public IBaseParameters mdmBatchPractitionerInstance(@IdParam IIdType theIdParam, RequestDetails theRequest) {
451                long submittedCount = synchronousMdmSubmit(null, theIdParam, null, theRequest);
452                return buildMdmOutParametersWithCount(submittedCount);
453        }
454
455        @Operation(
456                        name = ProviderConstants.OPERATION_MDM_SUBMIT,
457                        idempotent = false,
458                        typeName = "Practitioner",
459                        returnParameters = {
460                                @OperationParam(name = ProviderConstants.OPERATION_BATCH_RESPONSE_JOB_ID, typeName = "integer")
461                        })
462        public IBaseParameters mdmBatchPractitionerType(
463                        @OperationParam(name = ProviderConstants.MDM_BATCH_RUN_CRITERIA, typeName = "string")
464                                        IPrimitiveType<String> theCriteria,
465                        @OperationParam(name = ProviderConstants.OPERATION_MDM_BATCH_SIZE, typeName = "decimal", min = 0, max = 1)
466                                        IPrimitiveType<BigDecimal> theBatchSize,
467                        ServletRequestDetails theRequest) {
468                if (theRequest.isPreferRespondAsync()) {
469                        String theUrl = PRACTITIONER_RESOURCE + "?";
470                        return myMdmControllerSvc.submitMdmSubmitJob(Collections.singletonList(theUrl), theBatchSize, theRequest);
471                } else {
472                        String criteria = convertStringTypeToString(theCriteria);
473                        long submittedCount = synchronousMdmSubmit(PRACTITIONER_RESOURCE, null, criteria, theRequest);
474                        return buildMdmOutParametersWithCount(submittedCount);
475                }
476        }
477
478        /**
479         * Helper function to build the out-parameters for all batch MDM operations.
480         */
481        public IBaseParameters buildMdmOutParametersWithCount(long theCount) {
482                IBaseParameters retval = ParametersUtil.newInstance(myFhirContext);
483                ParametersUtil.addParameterToParametersLong(
484                                myFhirContext, retval, ProviderConstants.OPERATION_MDM_SUBMIT_OUT_PARAM_SUBMITTED, theCount);
485                return retval;
486        }
487
488        private String getResourceType(String theParamName, IPrimitiveType<String> theResourceId) {
489                if (theResourceId != null) {
490                        return getResourceType(theParamName, theResourceId.getValueAsString());
491                } else {
492                        return MdmConstants.UNKNOWN_MDM_TYPES;
493                }
494        }
495
496        private String getResourceType(
497                        String theParamName, IPrimitiveType<String> theResourceId, IPrimitiveType theResourceType) {
498                return theResourceType != null
499                                ? theResourceType.getValueAsString()
500                                : getResourceType(theParamName, theResourceId);
501        }
502
503        private String getResourceType(String theParamName, String theResourceId) {
504                if (StringUtils.isEmpty(theResourceId)) {
505                        return MdmConstants.UNKNOWN_MDM_TYPES;
506                }
507                IIdType idType = MdmControllerUtil.getGoldenIdDtOrThrowException(theParamName, theResourceId);
508                return idType.getResourceType();
509        }
510
511        private long synchronousMdmSubmit(
512                        String theResourceType, IIdType theIdType, String theCriteria, RequestDetails theRequestDetails) {
513                long submittedCount = -1;
514                List<String> urls = new ArrayList<>();
515                if (theIdType != null) {
516                        submittedCount = mdmSubmitWithId(theIdType, theRequestDetails, urls);
517                } else if (isNotBlank(theResourceType)) {
518                        submittedCount = submitResourceTypeWithCriteria(theResourceType, theCriteria, theRequestDetails, urls);
519                } else {
520                        submittedCount = submitAll(theCriteria, theRequestDetails, urls);
521                }
522
523                if (myInterceptorBroadcaster.hasHooks(Pointcut.MDM_SUBMIT)) {
524                        // MDM_SUBMIT synchronous submit
525                        MdmSubmitEvent submitEvent = new MdmSubmitEvent();
526                        submitEvent.setBatchJob(false);
527                        submitEvent.setUrls(urls);
528
529                        HookParams hookParams = new HookParams();
530                        hookParams.add(RequestDetails.class, theRequestDetails);
531                        hookParams.add(MdmSubmitEvent.class, submitEvent);
532                        myInterceptorBroadcaster.callHooks(Pointcut.MDM_SUBMIT, hookParams);
533                }
534
535                return submittedCount;
536        }
537
538        private long mdmSubmitWithId(IIdType theIdType, RequestDetails theRequestDetails, List<String> theUrls) {
539                theUrls.add(theIdType.getValue());
540                return myMdmSubmitSvc.submitSourceResourceToMdm(theIdType, theRequestDetails);
541        }
542
543        private long submitResourceTypeWithCriteria(
544                        String theResourceType, String theCriteria, RequestDetails theRequestDetails, List<String> theUrls) {
545                String url = theResourceType;
546                if (isNotEmpty(theCriteria)) {
547                        url += "?" + theCriteria;
548                }
549                theUrls.add(url);
550
551                return myMdmSubmitSvc.submitSourceResourceTypeToMdm(theResourceType, theCriteria, theRequestDetails);
552        }
553
554        private long submitAll(String theCriteria, RequestDetails theRequestDetails, List<String> theUrls) {
555                // submitAllSourceTypes only runs through
556                // valid MDM source types
557                List<String> resourceTypes = myMdmSettings.getMdmRules().getMdmTypes();
558                for (String resourceType : resourceTypes) {
559                        String url = resourceType + (isNotEmpty(theCriteria) ? "?" + theCriteria : "");
560                        theUrls.add(url);
561                }
562                return myMdmSubmitSvc.submitAllSourceTypesToMdm(theCriteria, theRequestDetails);
563        }
564}