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