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}