001package ca.uhn.fhir.jpa.mdm.svc;
002
003/*-
004 * #%L
005 * HAPI FHIR JPA Server - Master Data Management
006 * %%
007 * Copyright (C) 2014 - 2022 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.MdmLinkSourceEnum;
037import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
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.IIdType;
055import org.hl7.fhir.instance.model.api.IPrimitiveType;
056import org.springframework.beans.factory.annotation.Autowired;
057import org.springframework.data.domain.Page;
058import org.springframework.stereotype.Service;
059
060import javax.annotation.Nonnull;
061import javax.annotation.Nullable;
062import java.math.BigDecimal;
063import java.util.HashSet;
064import java.util.List;
065import java.util.Set;
066
067/**
068 * This class acts as a layer between MdmProviders and MDM services to support a REST API that's not a FHIR Operation API.
069 */
070@Service
071public class MdmControllerSvcImpl implements IMdmControllerSvc {
072
073        @Autowired
074        FhirContext myFhirContext;
075        @Autowired
076        MdmControllerHelper myMdmControllerHelper;
077        @Autowired
078        IGoldenResourceMergerSvc myGoldenResourceMergerSvc;
079        @Autowired
080        IMdmLinkQuerySvc myMdmLinkQuerySvc;
081        @Autowired
082        IMdmLinkUpdaterSvc myIMdmLinkUpdaterSvc;
083        @Autowired
084        IMdmLinkCreateSvc myIMdmLinkCreateSvc;
085        @Autowired
086        IRequestPartitionHelperSvc myRequestPartitionHelperSvc;
087        @Autowired
088        IJobCoordinator myJobCoordinator;
089
090        public MdmControllerSvcImpl() {
091        }
092
093        @Override
094        public IAnyResource mergeGoldenResources(String theFromGoldenResourceId, String theToGoldenResourceId, IAnyResource theManuallyMergedGoldenResource, MdmTransactionContext theMdmTransactionContext) {
095                IAnyResource fromGoldenResource = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(ProviderConstants.MDM_MERGE_GR_FROM_GOLDEN_RESOURCE_ID, theFromGoldenResourceId);
096                IAnyResource toGoldenResource = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(ProviderConstants.MDM_MERGE_GR_TO_GOLDEN_RESOURCE_ID, theToGoldenResourceId);
097                myMdmControllerHelper.validateMergeResources(fromGoldenResource, toGoldenResource);
098                myMdmControllerHelper.validateSameVersion(fromGoldenResource, theFromGoldenResourceId);
099                myMdmControllerHelper.validateSameVersion(toGoldenResource, theToGoldenResourceId);
100
101                return myGoldenResourceMergerSvc.mergeGoldenResources(fromGoldenResource, theManuallyMergedGoldenResource, toGoldenResource, theMdmTransactionContext);
102        }
103
104        @Override
105        public Page<MdmLinkJson> queryLinks(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest) {
106                return queryLinksFromPartitionList(theGoldenResourceId, theSourceResourceId, theMatchResult, theLinkSource, theMdmTransactionContext, thePageRequest, null);
107        }
108
109
110        @Override
111        public Page<MdmLinkJson> queryLinks(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest, @Nullable RequestDetails theRequestDetails) {
112                RequestPartitionId theReadPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, null, null);
113                Page<MdmLinkJson> resultPage;
114                if (theReadPartitionId.hasPartitionIds()) {
115                        resultPage = queryLinksFromPartitionList(theGoldenResourceId, theSourceResourceId, theMatchResult, theLinkSource, theMdmTransactionContext, thePageRequest, theReadPartitionId.getPartitionIds());
116                        validateMdmQueryPermissions(theReadPartitionId, resultPage.getContent(), theRequestDetails);
117                }
118                else {
119                        resultPage = queryLinksFromPartitionList(theGoldenResourceId, theSourceResourceId, theMatchResult, theLinkSource, theMdmTransactionContext, thePageRequest, null);
120                }
121
122                return resultPage;
123        }
124
125        @Override
126        public Page<MdmLinkJson> queryLinksFromPartitionList(@Nullable String theGoldenResourceId, @Nullable String theSourceResourceId, @Nullable String theMatchResult, @Nullable String theLinkSource, MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest, @Nullable List<Integer> thePartitionIds) {
127                IIdType goldenResourceId = MdmControllerUtil.extractGoldenResourceIdDtOrNull(ProviderConstants.MDM_QUERY_LINKS_GOLDEN_RESOURCE_ID, theGoldenResourceId);
128                IIdType sourceId = MdmControllerUtil.extractSourceIdDtOrNull(ProviderConstants.MDM_QUERY_LINKS_RESOURCE_ID, theSourceResourceId);
129                MdmMatchResultEnum matchResult = MdmControllerUtil.extractMatchResultOrNull(theMatchResult);
130                MdmLinkSourceEnum linkSource = MdmControllerUtil.extractLinkSourceOrNull(theLinkSource);
131
132                return myMdmLinkQuerySvc.queryLinks(goldenResourceId, sourceId, matchResult, linkSource, theMdmTransactionContext, thePageRequest, thePartitionIds);
133        }
134
135        @Override
136        public Page<MdmLinkJson> getDuplicateGoldenResources(MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest) {
137                return myMdmLinkQuerySvc.getDuplicateGoldenResources(theMdmTransactionContext, thePageRequest);
138        }
139
140        @Override
141        public Page<MdmLinkJson> getDuplicateGoldenResources(MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest, RequestDetails theRequestDetails) {
142                Page<MdmLinkJson> resultPage;
143                RequestPartitionId readPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, null, null);
144                if (readPartitionId.isAllPartitions()){
145                        resultPage =  myMdmLinkQuerySvc.getDuplicateGoldenResources(theMdmTransactionContext, thePageRequest);
146                }
147                else {
148                        resultPage =  myMdmLinkQuerySvc.getDuplicateGoldenResources(theMdmTransactionContext, thePageRequest, readPartitionId.getPartitionIds());
149                }
150
151                validateMdmQueryPermissions(readPartitionId, resultPage.getContent(), theRequestDetails);
152
153                return resultPage;
154        }
155
156        @Override
157        public IAnyResource updateLink(String theGoldenResourceId, String theSourceResourceId, String theMatchResult, MdmTransactionContext theMdmTransactionContext) {
158                MdmMatchResultEnum matchResult = MdmControllerUtil.extractMatchResultOrNull(theMatchResult);
159                IAnyResource goldenResource = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(ProviderConstants.MDM_UPDATE_LINK_GOLDEN_RESOURCE_ID, theGoldenResourceId);
160                IAnyResource source = myMdmControllerHelper.getLatestSourceFromIdOrThrowException(ProviderConstants.MDM_UPDATE_LINK_RESOURCE_ID, theSourceResourceId);
161                myMdmControllerHelper.validateSameVersion(goldenResource, theGoldenResourceId);
162                myMdmControllerHelper.validateSameVersion(source, theSourceResourceId);
163
164                return myIMdmLinkUpdaterSvc.updateLink(goldenResource, source, matchResult, theMdmTransactionContext);
165        }
166
167        @Override
168        public IAnyResource createLink(String theGoldenResourceId, String theSourceResourceId, @Nullable String theMatchResult, MdmTransactionContext theMdmTransactionContext) {
169                MdmMatchResultEnum matchResult = MdmControllerUtil.extractMatchResultOrNull(theMatchResult);
170                IAnyResource goldenResource = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(ProviderConstants.MDM_CREATE_LINK_GOLDEN_RESOURCE_ID, theGoldenResourceId);
171                IAnyResource source = myMdmControllerHelper.getLatestSourceFromIdOrThrowException(ProviderConstants.MDM_CREATE_LINK_RESOURCE_ID, theSourceResourceId);
172                myMdmControllerHelper.validateSameVersion(goldenResource, theGoldenResourceId);
173                myMdmControllerHelper.validateSameVersion(source, theSourceResourceId);
174
175                return myIMdmLinkCreateSvc.createLink(goldenResource, source, matchResult, theMdmTransactionContext);
176        }
177
178        @Override
179        public IBaseParameters submitMdmClearJob(@Nonnull List<String> theResourceNames, IPrimitiveType<BigDecimal> theBatchSize, ServletRequestDetails theRequestDetails) {
180                MdmClearJobParameters params = new MdmClearJobParameters();
181                params.setResourceNames(theResourceNames);
182                if (theBatchSize != null && theBatchSize.getValue() !=null && theBatchSize.getValue().longValue() > 0) {
183                        params.setBatchSize(theBatchSize.getValue().intValue());
184                }
185
186                ReadPartitionIdRequestDetails details= new ReadPartitionIdRequestDetails(null, RestOperationTypeEnum.EXTENDED_OPERATION_SERVER, null, null, null);
187                RequestPartitionId requestPartition = myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, null, details);
188                params.setRequestPartitionId(requestPartition);
189
190                JobInstanceStartRequest request = new JobInstanceStartRequest();
191                request.setJobDefinitionId(MdmClearAppCtx.JOB_MDM_CLEAR);
192                request.setParameters(params);
193                Batch2JobStartResponse response = myJobCoordinator.startInstance(request);
194                String id = response.getJobId();
195
196                IBaseParameters retVal = ParametersUtil.newInstance(myFhirContext);
197                ParametersUtil.addParameterToParametersString(myFhirContext, retVal, ProviderConstants.OPERATION_BATCH_RESPONSE_JOB_ID, id);
198                return retVal;
199        }
200
201
202        @Override
203        public IBaseParameters submitMdmSubmitJob(List<String> theUrls,  IPrimitiveType<BigDecimal> theBatchSize, ServletRequestDetails theRequestDetails) {
204                MdmSubmitJobParameters params = new MdmSubmitJobParameters();
205
206                if (theBatchSize != null && theBatchSize.getValue() !=null && theBatchSize.getValue().longValue() > 0) {
207                        params.setBatchSize(theBatchSize.getValue().intValue());
208                }
209                ReadPartitionIdRequestDetails details= new ReadPartitionIdRequestDetails(null, RestOperationTypeEnum.EXTENDED_OPERATION_SERVER, null, null, null);
210                params.setRequestPartitionId(RequestPartitionId.allPartitions());
211
212                theUrls.forEach(params::addUrl);
213
214                JobInstanceStartRequest request = new JobInstanceStartRequest();
215                request.setParameters(params);
216                request.setJobDefinitionId(MdmSubmitAppCtx.MDM_SUBMIT_JOB);
217
218                Batch2JobStartResponse batch2JobStartResponse = myJobCoordinator.startInstance(request);
219                String id = batch2JobStartResponse.getJobId();
220
221                IBaseParameters retVal = ParametersUtil.newInstance(myFhirContext);
222                ParametersUtil.addParameterToParametersString(myFhirContext, retVal, ProviderConstants.OPERATION_BATCH_RESPONSE_JOB_ID, id);
223                return retVal;
224        }
225
226        @Override
227        public void notDuplicateGoldenResource(String theGoldenResourceId, String theTargetGoldenResourceId, MdmTransactionContext theMdmTransactionContext) {
228                IAnyResource goldenResource = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(ProviderConstants.MDM_UPDATE_LINK_GOLDEN_RESOURCE_ID, theGoldenResourceId);
229                IAnyResource target = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(ProviderConstants.MDM_UPDATE_LINK_RESOURCE_ID, theTargetGoldenResourceId);
230
231                myIMdmLinkUpdaterSvc.notDuplicateGoldenResource(goldenResource, target, theMdmTransactionContext);
232        }
233
234        private void validateMdmQueryPermissions(RequestPartitionId theRequestPartitionId, List<MdmLinkJson> theMdmLinkJsonList, RequestDetails theRequestDetails){
235                Set<String> seenResourceTypes = new HashSet<>();
236                for (MdmLinkJson mdmLinkJson : theMdmLinkJsonList){
237                        IdDt idDt = new IdDt(mdmLinkJson.getSourceId());
238
239                        if (!seenResourceTypes.contains(idDt.getResourceType())){
240                                myRequestPartitionHelperSvc.validateHasPartitionPermissions(theRequestDetails, idDt.getResourceType(), theRequestPartitionId);
241                                seenResourceTypes.add(idDt.getResourceType());
242                        }
243                }
244        }
245}