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}