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}