001package ca.uhn.fhir.jpa.mdm.svc;
002
003/*-
004 * #%L
005 * HAPI FHIR JPA Server - Master Data Management
006 * %%
007 * Copyright (C) 2014 - 2023 Smile CDR, Inc.
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.batch2.api.IJobCoordinator;
024import ca.uhn.fhir.batch2.model.JobInstanceStartRequest;
025import ca.uhn.fhir.context.FhirContext;
026import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
027import ca.uhn.fhir.interceptor.model.RequestPartitionId;
028import ca.uhn.fhir.jpa.batch.models.Batch2JobStartResponse;
029import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
030import ca.uhn.fhir.mdm.api.IGoldenResourceMergerSvc;
031import ca.uhn.fhir.mdm.api.IMdmControllerSvc;
032import ca.uhn.fhir.mdm.api.IMdmLinkCreateSvc;
033import ca.uhn.fhir.mdm.api.IMdmLinkQuerySvc;
034import ca.uhn.fhir.mdm.api.IMdmLinkUpdaterSvc;
035import ca.uhn.fhir.mdm.api.MdmLinkJson;
036import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
037import ca.uhn.fhir.mdm.api.MdmQuerySearchParameters;
038import ca.uhn.fhir.mdm.api.paging.MdmPageRequest;
039import ca.uhn.fhir.mdm.batch2.clear.MdmClearAppCtx;
040import ca.uhn.fhir.mdm.batch2.clear.MdmClearJobParameters;
041import ca.uhn.fhir.mdm.batch2.submit.MdmSubmitAppCtx;
042import ca.uhn.fhir.mdm.batch2.submit.MdmSubmitJobParameters;
043import ca.uhn.fhir.mdm.model.MdmTransactionContext;
044import ca.uhn.fhir.mdm.provider.MdmControllerHelper;
045import ca.uhn.fhir.mdm.provider.MdmControllerUtil;
046import ca.uhn.fhir.model.primitive.IdDt;
047import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
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 javax.annotation.Nonnull;
060import javax.annotation.Nullable;
061import java.math.BigDecimal;
062import java.util.HashSet;
063import java.util.List;
064import java.util.Set;
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        @Autowired
075        MdmControllerHelper myMdmControllerHelper;
076        @Autowired
077        IGoldenResourceMergerSvc myGoldenResourceMergerSvc;
078        @Autowired
079        IMdmLinkQuerySvc myMdmLinkQuerySvc;
080        @Autowired
081        IMdmLinkUpdaterSvc myIMdmLinkUpdaterSvc;
082        @Autowired
083        IMdmLinkCreateSvc myIMdmLinkCreateSvc;
084        @Autowired
085        IRequestPartitionHelperSvc myRequestPartitionHelperSvc;
086        @Autowired
087        IJobCoordinator myJobCoordinator;
088
089        public MdmControllerSvcImpl() {
090        }
091
092        @Override
093        public IAnyResource mergeGoldenResources(String theFromGoldenResourceId, String theToGoldenResourceId, IAnyResource theManuallyMergedGoldenResource, MdmTransactionContext theMdmTransactionContext) {
094                IAnyResource fromGoldenResource = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(ProviderConstants.MDM_MERGE_GR_FROM_GOLDEN_RESOURCE_ID, theFromGoldenResourceId);
095                IAnyResource toGoldenResource = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(ProviderConstants.MDM_MERGE_GR_TO_GOLDEN_RESOURCE_ID, theToGoldenResourceId);
096                myMdmControllerHelper.validateMergeResources(fromGoldenResource, toGoldenResource);
097                myMdmControllerHelper.validateSameVersion(fromGoldenResource, theFromGoldenResourceId);
098                myMdmControllerHelper.validateSameVersion(toGoldenResource, theToGoldenResourceId);
099
100                return myGoldenResourceMergerSvc.mergeGoldenResources(fromGoldenResource, theManuallyMergedGoldenResource, toGoldenResource, theMdmTransactionContext);
101        }
102
103        @Override
104        @Deprecated
105        public Page<MdmLinkJson> queryLinks(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest) {
106                MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(thePageRequest)
107                        .setGoldenResourceId(theGoldenResourceId)
108                        .setSourceId(theSourceResourceId)
109                        .setMatchResult(theMatchResult)
110                        .setLinkSource(theLinkSource);
111                return queryLinksFromPartitionList(mdmQuerySearchParameters, theMdmTransactionContext);
112        }
113
114        @Override
115        @Deprecated
116        public Page<MdmLinkJson> queryLinks(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId,
117                                                                                                        @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext,
118                                                                                                        MdmPageRequest thePageRequest, @Nullable RequestDetails theRequestDetails) {
119                MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(thePageRequest)
120                        .setGoldenResourceId(theGoldenResourceId)
121                        .setSourceId(theSourceResourceId)
122                        .setMatchResult(theMatchResult)
123                        .setLinkSource(theLinkSource);
124                return queryLinks(mdmQuerySearchParameters, theMdmTransactionContext, theRequestDetails);
125        }
126
127        @Override
128        public Page<MdmLinkJson> queryLinks(MdmQuerySearchParameters theMdmQuerySearchParameters, MdmTransactionContext theMdmTransactionContext, RequestDetails theRequestDetails)
129        {
130                RequestPartitionId theReadPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, null, null);
131                Page<MdmLinkJson> resultPage;
132                if (theReadPartitionId.hasPartitionIds()) {
133                        theMdmQuerySearchParameters.setPartitionIds(theReadPartitionId.getPartitionIds());
134                }
135                resultPage = queryLinksFromPartitionList(theMdmQuerySearchParameters, theMdmTransactionContext);
136                validateMdmQueryPermissions(theReadPartitionId, resultPage.getContent(), theRequestDetails);
137
138                return resultPage;
139        }
140
141        @Override
142        @Deprecated
143        public Page<MdmLinkJson> queryLinksFromPartitionList(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId,
144                                                                                                                                                  @Nullable String theMatchResult, @Nullable String theLinkSource,
145                                                                                                                                                  MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest,
146                                                                                                                                                  @Nullable List<Integer> thePartitionIds) {
147                MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(thePageRequest)
148                        .setGoldenResourceId(theGoldenResourceId)
149                        .setSourceId(theSourceResourceId)
150                        .setMatchResult(theMatchResult)
151                        .setLinkSource(theLinkSource)
152                        .setPartitionIds(thePartitionIds);
153                return queryLinksFromPartitionList(mdmQuerySearchParameters, theMdmTransactionContext);
154        }
155
156        @Override
157        public Page<MdmLinkJson> queryLinksFromPartitionList(MdmQuerySearchParameters theMdmQuerySearchParameters, MdmTransactionContext theMdmTransactionContext) {
158                return myMdmLinkQuerySvc.queryLinks(theMdmQuerySearchParameters, theMdmTransactionContext);
159        }
160
161        @Override
162        public Page<MdmLinkJson> getDuplicateGoldenResources(MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest) {
163                return myMdmLinkQuerySvc.getDuplicateGoldenResources(theMdmTransactionContext, thePageRequest, null, null);
164        }
165
166        @Override
167        public Page<MdmLinkJson> getDuplicateGoldenResources(MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest, RequestDetails theRequestDetails, String theRequestResourceType) {
168                Page<MdmLinkJson> resultPage;
169                RequestPartitionId readPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, null, null);
170
171                if (readPartitionId.isAllPartitions()){
172                        resultPage =  myMdmLinkQuerySvc.getDuplicateGoldenResources(theMdmTransactionContext, thePageRequest, null, theRequestResourceType);
173                } else {
174                        resultPage =  myMdmLinkQuerySvc.getDuplicateGoldenResources(theMdmTransactionContext, thePageRequest, readPartitionId.getPartitionIds(), theRequestResourceType);
175                }
176
177                validateMdmQueryPermissions(readPartitionId, resultPage.getContent(), theRequestDetails);
178
179                return resultPage;
180        }
181
182        @Override
183        public IAnyResource updateLink(String theGoldenResourceId, String theSourceResourceId, String theMatchResult, MdmTransactionContext theMdmTransactionContext) {
184                MdmMatchResultEnum matchResult = MdmControllerUtil.extractMatchResultOrNull(theMatchResult);
185                IAnyResource goldenResource = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(ProviderConstants.MDM_UPDATE_LINK_GOLDEN_RESOURCE_ID, theGoldenResourceId);
186                IAnyResource source = myMdmControllerHelper.getLatestSourceFromIdOrThrowException(ProviderConstants.MDM_UPDATE_LINK_RESOURCE_ID, theSourceResourceId);
187                myMdmControllerHelper.validateSameVersion(goldenResource, theGoldenResourceId);
188                myMdmControllerHelper.validateSameVersion(source, theSourceResourceId);
189
190                return myIMdmLinkUpdaterSvc.updateLink(goldenResource, source, matchResult, theMdmTransactionContext);
191        }
192
193        @Override
194        public IAnyResource createLink(String theGoldenResourceId, String theSourceResourceId, @Nullable String theMatchResult, MdmTransactionContext theMdmTransactionContext) {
195                MdmMatchResultEnum matchResult = MdmControllerUtil.extractMatchResultOrNull(theMatchResult);
196                IAnyResource goldenResource = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(ProviderConstants.MDM_CREATE_LINK_GOLDEN_RESOURCE_ID, theGoldenResourceId);
197                IAnyResource source = myMdmControllerHelper.getLatestSourceFromIdOrThrowException(ProviderConstants.MDM_CREATE_LINK_RESOURCE_ID, theSourceResourceId);
198                myMdmControllerHelper.validateSameVersion(goldenResource, theGoldenResourceId);
199                myMdmControllerHelper.validateSameVersion(source, theSourceResourceId);
200
201                return myIMdmLinkCreateSvc.createLink(goldenResource, source, matchResult, theMdmTransactionContext);
202        }
203
204        @Override
205        public IBaseParameters submitMdmClearJob(@Nonnull List<String> theResourceNames, IPrimitiveType<BigDecimal> theBatchSize, ServletRequestDetails theRequestDetails) {
206                MdmClearJobParameters params = new MdmClearJobParameters();
207                params.setResourceNames(theResourceNames);
208                if (theBatchSize != null && theBatchSize.getValue() !=null && theBatchSize.getValue().longValue() > 0) {
209                        params.setBatchSize(theBatchSize.getValue().intValue());
210                }
211
212                ReadPartitionIdRequestDetails details= new ReadPartitionIdRequestDetails(null, RestOperationTypeEnum.EXTENDED_OPERATION_SERVER, null, null, null);
213                RequestPartitionId requestPartition = myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, null, details);
214                params.setRequestPartitionId(requestPartition);
215
216                JobInstanceStartRequest request = new JobInstanceStartRequest();
217                request.setJobDefinitionId(MdmClearAppCtx.JOB_MDM_CLEAR);
218                request.setParameters(params);
219                Batch2JobStartResponse response = myJobCoordinator.startInstance(request);
220                String id = response.getJobId();
221
222                IBaseParameters retVal = ParametersUtil.newInstance(myFhirContext);
223                ParametersUtil.addParameterToParametersString(myFhirContext, retVal, ProviderConstants.OPERATION_BATCH_RESPONSE_JOB_ID, id);
224                return retVal;
225        }
226
227
228        @Override
229        public IBaseParameters submitMdmSubmitJob(List<String> theUrls,  IPrimitiveType<BigDecimal> theBatchSize, ServletRequestDetails theRequestDetails) {
230                MdmSubmitJobParameters params = new MdmSubmitJobParameters();
231
232                if (theBatchSize != null && theBatchSize.getValue() !=null && theBatchSize.getValue().longValue() > 0) {
233                        params.setBatchSize(theBatchSize.getValue().intValue());
234                }
235                params.setRequestPartitionId(RequestPartitionId.allPartitions());
236
237                theUrls.forEach(params::addUrl);
238
239                JobInstanceStartRequest request = new JobInstanceStartRequest();
240                request.setParameters(params);
241                request.setJobDefinitionId(MdmSubmitAppCtx.MDM_SUBMIT_JOB);
242
243                Batch2JobStartResponse batch2JobStartResponse = myJobCoordinator.startInstance(request);
244                String id = batch2JobStartResponse.getJobId();
245
246                IBaseParameters retVal = ParametersUtil.newInstance(myFhirContext);
247                ParametersUtil.addParameterToParametersString(myFhirContext, retVal, ProviderConstants.OPERATION_BATCH_RESPONSE_JOB_ID, id);
248                return retVal;
249        }
250
251        @Override
252        public void notDuplicateGoldenResource(String theGoldenResourceId, String theTargetGoldenResourceId, MdmTransactionContext theMdmTransactionContext) {
253                IAnyResource goldenResource = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(ProviderConstants.MDM_UPDATE_LINK_GOLDEN_RESOURCE_ID, theGoldenResourceId);
254                IAnyResource target = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(ProviderConstants.MDM_UPDATE_LINK_RESOURCE_ID, theTargetGoldenResourceId);
255
256                myIMdmLinkUpdaterSvc.notDuplicateGoldenResource(goldenResource, target, theMdmTransactionContext);
257        }
258
259        private void validateMdmQueryPermissions(RequestPartitionId theRequestPartitionId, List<MdmLinkJson> theMdmLinkJsonList, RequestDetails theRequestDetails){
260                Set<String> seenResourceTypes = new HashSet<>();
261                for (MdmLinkJson mdmLinkJson : theMdmLinkJsonList){
262                        IdDt idDt = new IdDt(mdmLinkJson.getSourceId());
263
264                        if (!seenResourceTypes.contains(idDt.getResourceType())){
265                                myRequestPartitionHelperSvc.validateHasPartitionPermissions(theRequestDetails, idDt.getResourceType(), theRequestPartitionId);
266                                seenResourceTypes.add(idDt.getResourceType());
267                        }
268                }
269        }
270}