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