001/*-
002 * #%L
003 * HAPI FHIR JPA Server - Master Data Management
004 * %%
005 * Copyright (C) 2014 - 2023 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.jpa.mdm.svc;
021
022import ca.uhn.fhir.batch2.api.IJobCoordinator;
023import ca.uhn.fhir.batch2.model.JobInstanceStartRequest;
024import ca.uhn.fhir.context.FhirContext;
025import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
026import ca.uhn.fhir.interceptor.model.RequestPartitionId;
027import ca.uhn.fhir.jpa.batch.models.Batch2JobStartResponse;
028import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
029import ca.uhn.fhir.mdm.api.IGoldenResourceMergerSvc;
030import ca.uhn.fhir.mdm.api.IMdmControllerSvc;
031import ca.uhn.fhir.mdm.api.IMdmLinkCreateSvc;
032import ca.uhn.fhir.mdm.api.IMdmLinkQuerySvc;
033import ca.uhn.fhir.mdm.api.IMdmLinkUpdaterSvc;
034import ca.uhn.fhir.mdm.api.MdmHistorySearchParameters;
035import ca.uhn.fhir.mdm.api.MdmLinkJson;
036import ca.uhn.fhir.mdm.api.MdmLinkWithRevisionJson;
037import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
038import ca.uhn.fhir.mdm.api.MdmQuerySearchParameters;
039import ca.uhn.fhir.mdm.api.paging.MdmPageRequest;
040import ca.uhn.fhir.mdm.batch2.clear.MdmClearAppCtx;
041import ca.uhn.fhir.mdm.batch2.clear.MdmClearJobParameters;
042import ca.uhn.fhir.mdm.batch2.submit.MdmSubmitAppCtx;
043import ca.uhn.fhir.mdm.batch2.submit.MdmSubmitJobParameters;
044import ca.uhn.fhir.mdm.model.MdmTransactionContext;
045import ca.uhn.fhir.mdm.provider.MdmControllerHelper;
046import ca.uhn.fhir.mdm.provider.MdmControllerUtil;
047import ca.uhn.fhir.model.primitive.IdDt;
048import ca.uhn.fhir.rest.api.server.RequestDetails;
049import ca.uhn.fhir.rest.server.provider.ProviderConstants;
050import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
051import ca.uhn.fhir.util.ParametersUtil;
052import org.hl7.fhir.instance.model.api.IAnyResource;
053import org.hl7.fhir.instance.model.api.IBaseParameters;
054import org.hl7.fhir.instance.model.api.IPrimitiveType;
055import org.springframework.beans.factory.annotation.Autowired;
056import org.springframework.data.domain.Page;
057import org.springframework.stereotype.Service;
058
059import java.math.BigDecimal;
060import java.util.HashSet;
061import java.util.List;
062import java.util.Set;
063import javax.annotation.Nonnull;
064import javax.annotation.Nullable;
065
066/**
067 * This class acts as a layer between MdmProviders and MDM services to support a REST API that's not a FHIR Operation API.
068 */
069@Service
070public class MdmControllerSvcImpl implements IMdmControllerSvc {
071
072        @Autowired
073        FhirContext myFhirContext;
074
075        @Autowired
076        MdmControllerHelper myMdmControllerHelper;
077
078        @Autowired
079        IGoldenResourceMergerSvc myGoldenResourceMergerSvc;
080
081        @Autowired
082        IMdmLinkQuerySvc myMdmLinkQuerySvc;
083
084        @Autowired
085        IMdmLinkUpdaterSvc myIMdmLinkUpdaterSvc;
086
087        @Autowired
088        IMdmLinkCreateSvc myIMdmLinkCreateSvc;
089
090        @Autowired
091        IRequestPartitionHelperSvc myRequestPartitionHelperSvc;
092
093        @Autowired
094        IJobCoordinator myJobCoordinator;
095
096        public MdmControllerSvcImpl() {}
097
098        @Override
099        public IAnyResource mergeGoldenResources(
100                        String theFromGoldenResourceId,
101                        String theToGoldenResourceId,
102                        IAnyResource theManuallyMergedGoldenResource,
103                        MdmTransactionContext theMdmTransactionContext) {
104                IAnyResource fromGoldenResource = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(
105                                ProviderConstants.MDM_MERGE_GR_FROM_GOLDEN_RESOURCE_ID, theFromGoldenResourceId);
106                IAnyResource toGoldenResource = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(
107                                ProviderConstants.MDM_MERGE_GR_TO_GOLDEN_RESOURCE_ID, theToGoldenResourceId);
108                myMdmControllerHelper.validateMergeResources(fromGoldenResource, toGoldenResource);
109                myMdmControllerHelper.validateSameVersion(fromGoldenResource, theFromGoldenResourceId);
110                myMdmControllerHelper.validateSameVersion(toGoldenResource, theToGoldenResourceId);
111
112                return myGoldenResourceMergerSvc.mergeGoldenResources(
113                                fromGoldenResource, theManuallyMergedGoldenResource, toGoldenResource, theMdmTransactionContext);
114        }
115
116        @Override
117        @Deprecated
118        public Page<MdmLinkJson> queryLinks(
119                        @Nullable String theGoldenResourceId,
120                        @Nullable String theSourceResourceId,
121                        @Nullable String theMatchResult,
122                        @Nullable String theLinkSource,
123                        MdmTransactionContext theMdmTransactionContext,
124                        MdmPageRequest thePageRequest) {
125                MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(thePageRequest)
126                                .setGoldenResourceId(theGoldenResourceId)
127                                .setSourceId(theSourceResourceId)
128                                .setMatchResult(theMatchResult)
129                                .setLinkSource(theLinkSource);
130                return queryLinksFromPartitionList(mdmQuerySearchParameters, theMdmTransactionContext);
131        }
132
133        @Override
134        @Deprecated
135        public Page<MdmLinkJson> queryLinks(
136                        @Nullable String theGoldenResourceId,
137                        @Nullable String theSourceResourceId,
138                        @Nullable String theMatchResult,
139                        @Nullable String theLinkSource,
140                        MdmTransactionContext theMdmTransactionContext,
141                        MdmPageRequest thePageRequest,
142                        @Nullable RequestDetails theRequestDetails) {
143                MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(thePageRequest)
144                                .setGoldenResourceId(theGoldenResourceId)
145                                .setSourceId(theSourceResourceId)
146                                .setMatchResult(theMatchResult)
147                                .setLinkSource(theLinkSource);
148                return queryLinks(mdmQuerySearchParameters, theMdmTransactionContext, theRequestDetails);
149        }
150
151        @Override
152        public Page<MdmLinkJson> queryLinks(
153                        MdmQuerySearchParameters theMdmQuerySearchParameters,
154                        MdmTransactionContext theMdmTransactionContext,
155                        RequestDetails theRequestDetails) {
156                RequestPartitionId theReadPartitionId =
157                                myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, null);
158                Page<MdmLinkJson> resultPage;
159                if (theReadPartitionId.hasPartitionIds()) {
160                        theMdmQuerySearchParameters.setPartitionIds(theReadPartitionId.getPartitionIds());
161                }
162                resultPage = queryLinksFromPartitionList(theMdmQuerySearchParameters, theMdmTransactionContext);
163                validateMdmQueryPermissions(theReadPartitionId, resultPage.getContent(), theRequestDetails);
164
165                return resultPage;
166        }
167
168        @Override
169        public List<MdmLinkWithRevisionJson> queryLinkHistory(
170                        MdmHistorySearchParameters theMdmHistorySearchParameters, RequestDetails theRequestDetails) {
171                return myMdmLinkQuerySvc.queryLinkHistory(theMdmHistorySearchParameters);
172        }
173
174        @Override
175        @Deprecated
176        public Page<MdmLinkJson> queryLinksFromPartitionList(
177                        @Nullable String theGoldenResourceId,
178                        @Nullable String theSourceResourceId,
179                        @Nullable String theMatchResult,
180                        @Nullable String theLinkSource,
181                        MdmTransactionContext theMdmTransactionContext,
182                        MdmPageRequest thePageRequest,
183                        @Nullable List<Integer> thePartitionIds) {
184                MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(thePageRequest)
185                                .setGoldenResourceId(theGoldenResourceId)
186                                .setSourceId(theSourceResourceId)
187                                .setMatchResult(theMatchResult)
188                                .setLinkSource(theLinkSource)
189                                .setPartitionIds(thePartitionIds);
190                return queryLinksFromPartitionList(mdmQuerySearchParameters, theMdmTransactionContext);
191        }
192
193        @Override
194        public Page<MdmLinkJson> queryLinksFromPartitionList(
195                        MdmQuerySearchParameters theMdmQuerySearchParameters, MdmTransactionContext theMdmTransactionContext) {
196                return myMdmLinkQuerySvc.queryLinks(theMdmQuerySearchParameters, theMdmTransactionContext);
197        }
198
199        @Override
200        public Page<MdmLinkJson> getDuplicateGoldenResources(
201                        MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest) {
202                return myMdmLinkQuerySvc.getDuplicateGoldenResources(theMdmTransactionContext, thePageRequest, null, null);
203        }
204
205        @Override
206        public Page<MdmLinkJson> getDuplicateGoldenResources(
207                        MdmTransactionContext theMdmTransactionContext,
208                        MdmPageRequest thePageRequest,
209                        RequestDetails theRequestDetails,
210                        String theRequestResourceType) {
211                Page<MdmLinkJson> resultPage;
212                RequestPartitionId readPartitionId =
213                                myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, null);
214
215                if (readPartitionId.isAllPartitions()) {
216                        resultPage = myMdmLinkQuerySvc.getDuplicateGoldenResources(
217                                        theMdmTransactionContext, thePageRequest, null, theRequestResourceType);
218                } else {
219                        resultPage = myMdmLinkQuerySvc.getDuplicateGoldenResources(
220                                        theMdmTransactionContext,
221                                        thePageRequest,
222                                        readPartitionId.getPartitionIds(),
223                                        theRequestResourceType);
224                }
225
226                validateMdmQueryPermissions(readPartitionId, resultPage.getContent(), theRequestDetails);
227
228                return resultPage;
229        }
230
231        @Override
232        public IAnyResource updateLink(
233                        String theGoldenResourceId,
234                        String theSourceResourceId,
235                        String theMatchResult,
236                        MdmTransactionContext theMdmTransactionContext) {
237                MdmMatchResultEnum matchResult = MdmControllerUtil.extractMatchResultOrNull(theMatchResult);
238                IAnyResource goldenResource = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(
239                                ProviderConstants.MDM_UPDATE_LINK_GOLDEN_RESOURCE_ID, theGoldenResourceId);
240                IAnyResource source = myMdmControllerHelper.getLatestSourceFromIdOrThrowException(
241                                ProviderConstants.MDM_UPDATE_LINK_RESOURCE_ID, theSourceResourceId);
242                myMdmControllerHelper.validateSameVersion(goldenResource, theGoldenResourceId);
243                myMdmControllerHelper.validateSameVersion(source, theSourceResourceId);
244
245                return myIMdmLinkUpdaterSvc.updateLink(goldenResource, source, matchResult, theMdmTransactionContext);
246        }
247
248        @Override
249        public IAnyResource createLink(
250                        String theGoldenResourceId,
251                        String theSourceResourceId,
252                        @Nullable String theMatchResult,
253                        MdmTransactionContext theMdmTransactionContext) {
254                MdmMatchResultEnum matchResult = MdmControllerUtil.extractMatchResultOrNull(theMatchResult);
255                IAnyResource goldenResource = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(
256                                ProviderConstants.MDM_CREATE_LINK_GOLDEN_RESOURCE_ID, theGoldenResourceId);
257                IAnyResource source = myMdmControllerHelper.getLatestSourceFromIdOrThrowException(
258                                ProviderConstants.MDM_CREATE_LINK_RESOURCE_ID, theSourceResourceId);
259                myMdmControllerHelper.validateSameVersion(goldenResource, theGoldenResourceId);
260                myMdmControllerHelper.validateSameVersion(source, theSourceResourceId);
261
262                return myIMdmLinkCreateSvc.createLink(goldenResource, source, matchResult, theMdmTransactionContext);
263        }
264
265        @Override
266        public IBaseParameters submitMdmClearJob(
267                        @Nonnull List<String> theResourceNames,
268                        IPrimitiveType<BigDecimal> theBatchSize,
269                        ServletRequestDetails theRequestDetails) {
270                MdmClearJobParameters params = new MdmClearJobParameters();
271                params.setResourceNames(theResourceNames);
272                if (theBatchSize != null
273                                && theBatchSize.getValue() != null
274                                && theBatchSize.getValue().longValue() > 0) {
275                        params.setBatchSize(theBatchSize.getValue().intValue());
276                }
277
278                ReadPartitionIdRequestDetails details =
279                                ReadPartitionIdRequestDetails.forOperation(null, null, ProviderConstants.OPERATION_MDM_CLEAR);
280                RequestPartitionId requestPartition =
281                                myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, details);
282                params.setRequestPartitionId(requestPartition);
283
284                JobInstanceStartRequest request = new JobInstanceStartRequest();
285                request.setJobDefinitionId(MdmClearAppCtx.JOB_MDM_CLEAR);
286                request.setParameters(params);
287                Batch2JobStartResponse response = myJobCoordinator.startInstance(theRequestDetails, request);
288                String id = response.getInstanceId();
289
290                IBaseParameters retVal = ParametersUtil.newInstance(myFhirContext);
291                ParametersUtil.addParameterToParametersString(
292                                myFhirContext, retVal, ProviderConstants.OPERATION_BATCH_RESPONSE_JOB_ID, id);
293                return retVal;
294        }
295
296        @Override
297        public IBaseParameters submitMdmSubmitJob(
298                        List<String> theUrls, IPrimitiveType<BigDecimal> theBatchSize, ServletRequestDetails theRequestDetails) {
299                MdmSubmitJobParameters params = new MdmSubmitJobParameters();
300
301                if (theBatchSize != null
302                                && theBatchSize.getValue() != null
303                                && theBatchSize.getValue().longValue() > 0) {
304                        params.setBatchSize(theBatchSize.getValue().intValue());
305                }
306                params.setRequestPartitionId(RequestPartitionId.allPartitions());
307
308                theUrls.forEach(params::addUrl);
309
310                JobInstanceStartRequest request = new JobInstanceStartRequest();
311                request.setParameters(params);
312                request.setJobDefinitionId(MdmSubmitAppCtx.MDM_SUBMIT_JOB);
313
314                Batch2JobStartResponse batch2JobStartResponse = myJobCoordinator.startInstance(theRequestDetails, request);
315                String id = batch2JobStartResponse.getInstanceId();
316
317                IBaseParameters retVal = ParametersUtil.newInstance(myFhirContext);
318                ParametersUtil.addParameterToParametersString(
319                                myFhirContext, retVal, ProviderConstants.OPERATION_BATCH_RESPONSE_JOB_ID, id);
320                return retVal;
321        }
322
323        @Override
324        public void notDuplicateGoldenResource(
325                        String theGoldenResourceId,
326                        String theTargetGoldenResourceId,
327                        MdmTransactionContext theMdmTransactionContext) {
328                IAnyResource goldenResource = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(
329                                ProviderConstants.MDM_UPDATE_LINK_GOLDEN_RESOURCE_ID, theGoldenResourceId);
330                IAnyResource target = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(
331                                ProviderConstants.MDM_UPDATE_LINK_RESOURCE_ID, theTargetGoldenResourceId);
332
333                myIMdmLinkUpdaterSvc.notDuplicateGoldenResource(goldenResource, target, theMdmTransactionContext);
334        }
335
336        private void validateMdmQueryPermissions(
337                        RequestPartitionId theRequestPartitionId,
338                        List<MdmLinkJson> theMdmLinkJsonList,
339                        RequestDetails theRequestDetails) {
340                Set<String> seenResourceTypes = new HashSet<>();
341                for (MdmLinkJson mdmLinkJson : theMdmLinkJsonList) {
342                        IdDt idDt = new IdDt(mdmLinkJson.getSourceId());
343
344                        if (!seenResourceTypes.contains(idDt.getResourceType())) {
345                                myRequestPartitionHelperSvc.validateHasPartitionPermissions(
346                                                theRequestDetails, idDt.getResourceType(), theRequestPartitionId);
347                                seenResourceTypes.add(idDt.getResourceType());
348                        }
349                }
350        }
351}