All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ca.uhn.fhir.jpa.mdm.svc.MdmControllerSvcImpl Maven / Gradle / Ivy

The newest version!
/*-
 * #%L
 * HAPI FHIR JPA Server - Master Data Management
 * %%
 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package ca.uhn.fhir.jpa.mdm.svc;

import ca.uhn.fhir.batch2.api.IJobCoordinator;
import ca.uhn.fhir.batch2.jobs.parameters.PartitionedUrl;
import ca.uhn.fhir.batch2.model.JobInstanceStartRequest;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.batch.models.Batch2JobStartResponse;
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.mdm.api.IGoldenResourceMergerSvc;
import ca.uhn.fhir.mdm.api.IMdmControllerSvc;
import ca.uhn.fhir.mdm.api.IMdmLinkCreateSvc;
import ca.uhn.fhir.mdm.api.IMdmLinkQuerySvc;
import ca.uhn.fhir.mdm.api.IMdmLinkUpdaterSvc;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.api.paging.MdmPageRequest;
import ca.uhn.fhir.mdm.api.params.MdmHistorySearchParameters;
import ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters;
import ca.uhn.fhir.mdm.batch2.clear.MdmClearAppCtx;
import ca.uhn.fhir.mdm.batch2.clear.MdmClearJobParameters;
import ca.uhn.fhir.mdm.batch2.submit.MdmSubmitAppCtx;
import ca.uhn.fhir.mdm.batch2.submit.MdmSubmitJobParameters;
import ca.uhn.fhir.mdm.model.MdmCreateOrUpdateParams;
import ca.uhn.fhir.mdm.model.MdmMergeGoldenResourcesParams;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import ca.uhn.fhir.mdm.model.MdmUnduplicateGoldenResourceParams;
import ca.uhn.fhir.mdm.model.mdmevents.MdmClearEvent;
import ca.uhn.fhir.mdm.model.mdmevents.MdmLinkJson;
import ca.uhn.fhir.mdm.model.mdmevents.MdmLinkWithRevisionJson;
import ca.uhn.fhir.mdm.model.mdmevents.MdmSubmitEvent;
import ca.uhn.fhir.mdm.provider.MdmControllerHelper;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.ParametersUtil;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * This class acts as a layer between MdmProviders and MDM services to support a REST API that's not a FHIR Operation API.
 */
@Service
public class MdmControllerSvcImpl implements IMdmControllerSvc {

	@Autowired
	FhirContext myFhirContext;

	@Autowired
	MdmControllerHelper myMdmControllerHelper;

	@Autowired
	IGoldenResourceMergerSvc myGoldenResourceMergerSvc;

	@Autowired
	IMdmLinkQuerySvc myMdmLinkQuerySvc;

	@Autowired
	IMdmLinkUpdaterSvc myIMdmLinkUpdaterSvc;

	@Autowired
	IMdmLinkCreateSvc myIMdmLinkCreateSvc;

	@Autowired
	IRequestPartitionHelperSvc myRequestPartitionHelperSvc;

	@Autowired
	IJobCoordinator myJobCoordinator;

	@Autowired
	IInterceptorBroadcaster myInterceptorBroadcaster;

	@Autowired
	private HapiTransactionService myTxService;

	public MdmControllerSvcImpl() {}

	@Override
	public IAnyResource mergeGoldenResources(MdmMergeGoldenResourcesParams theParams) {
		if (theParams.getFromGoldenResource() == null) {
			theParams.setFromGoldenResource(myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(
					ProviderConstants.MDM_MERGE_GR_FROM_GOLDEN_RESOURCE_ID, theParams.getFromGoldenResourceId()));
		}
		IAnyResource fromGoldenResource = theParams.getFromGoldenResource();
		;
		if (theParams.getToGoldenResource() == null) {
			theParams.setToGoldenResource(myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(
					ProviderConstants.MDM_MERGE_GR_TO_GOLDEN_RESOURCE_ID, theParams.getToGoldenResourceId()));
		}
		IAnyResource toGoldenResource = theParams.getToGoldenResource();
		myMdmControllerHelper.validateMergeResources(fromGoldenResource, toGoldenResource);
		myMdmControllerHelper.validateSameVersion(fromGoldenResource, theParams.getFromGoldenResourceId());
		myMdmControllerHelper.validateSameVersion(toGoldenResource, theParams.getToGoldenResourceId());

		return myGoldenResourceMergerSvc.mergeGoldenResources(theParams);
	}

	@Override
	public IAnyResource updateLink(
			String theGoldenResourceId,
			String theSourceResourceId,
			String theMatchResult,
			MdmTransactionContext theMdmTransactionContext) {
		MdmCreateOrUpdateParams params = new MdmCreateOrUpdateParams();
		params.setResourceId(theSourceResourceId);
		params.setGoldenResourceId(theGoldenResourceId);
		params.setMdmContext(theMdmTransactionContext);
		params.setMatchResult(MdmMatchResultEnum.valueOf(theMatchResult));
		return updateLink(params);
	}

	@Override
	@Deprecated
	public Page queryLinks(
			@Nullable String theGoldenResourceId,
			@Nullable String theSourceResourceId,
			@Nullable String theMatchResult,
			@Nullable String theLinkSource,
			MdmTransactionContext theMdmTransactionContext,
			MdmPageRequest thePageRequest) {
		MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(thePageRequest)
				.setGoldenResourceId(theGoldenResourceId)
				.setSourceId(theSourceResourceId)
				.setMatchResult(theMatchResult)
				.setLinkSource(theLinkSource);
		return queryLinksFromPartitionList(mdmQuerySearchParameters, theMdmTransactionContext);
	}

	@Override
	@Deprecated
	public Page queryLinks(
			@Nullable String theGoldenResourceId,
			@Nullable String theSourceResourceId,
			@Nullable String theMatchResult,
			@Nullable String theLinkSource,
			MdmTransactionContext theMdmTransactionContext,
			MdmPageRequest thePageRequest,
			@Nullable RequestDetails theRequestDetails) {
		MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(thePageRequest)
				.setGoldenResourceId(theGoldenResourceId)
				.setSourceId(theSourceResourceId)
				.setMatchResult(theMatchResult)
				.setLinkSource(theLinkSource);
		return queryLinks(mdmQuerySearchParameters, theMdmTransactionContext, theRequestDetails);
	}

	@Override
	public Page queryLinks(
			MdmQuerySearchParameters theMdmQuerySearchParameters,
			MdmTransactionContext theMdmTransactionContext,
			RequestDetails theRequestDetails) {
		RequestPartitionId theReadPartitionId =
				myRequestPartitionHelperSvc.determineReadPartitionForRequestForServerOperation(
						theRequestDetails, ProviderConstants.MDM_QUERY_LINKS);
		Page resultPage;
		if (theReadPartitionId.hasPartitionIds()) {
			theMdmQuerySearchParameters.setPartitionIds(theReadPartitionId.getPartitionIds());
		}
		resultPage = queryLinksFromPartitionList(theMdmQuerySearchParameters, theMdmTransactionContext);
		validateMdmQueryPermissions(theReadPartitionId, resultPage.getContent(), theRequestDetails);

		return resultPage;
	}

	@Override
	public List queryLinkHistory(
			MdmHistorySearchParameters theMdmHistorySearchParameters, RequestDetails theRequestDetails) {
		return myTxService
				.withRequest(theRequestDetails)
				.execute(() -> myMdmLinkQuerySvc.queryLinkHistory(theMdmHistorySearchParameters));
	}

	@Override
	@Deprecated
	public Page queryLinksFromPartitionList(
			@Nullable String theGoldenResourceId,
			@Nullable String theSourceResourceId,
			@Nullable String theMatchResult,
			@Nullable String theLinkSource,
			MdmTransactionContext theMdmTransactionContext,
			MdmPageRequest thePageRequest,
			@Nullable List thePartitionIds) {
		MdmQuerySearchParameters mdmQuerySearchParameters = new MdmQuerySearchParameters(thePageRequest)
				.setGoldenResourceId(theGoldenResourceId)
				.setSourceId(theSourceResourceId)
				.setMatchResult(theMatchResult)
				.setLinkSource(theLinkSource)
				.setPartitionIds(thePartitionIds);
		return queryLinksFromPartitionList(mdmQuerySearchParameters, theMdmTransactionContext);
	}

	@Override
	public Page queryLinksFromPartitionList(
			MdmQuerySearchParameters theMdmQuerySearchParameters, MdmTransactionContext theMdmTransactionContext) {
		return myMdmLinkQuerySvc.queryLinks(theMdmQuerySearchParameters, theMdmTransactionContext);
	}

	@Override
	public Page getDuplicateGoldenResources(
			MdmTransactionContext theMdmTransactionContext, MdmPageRequest thePageRequest) {
		return myMdmLinkQuerySvc.getDuplicateGoldenResources(theMdmTransactionContext, thePageRequest, null, null);
	}

	@Override
	public Page getDuplicateGoldenResources(
			MdmTransactionContext theMdmTransactionContext,
			MdmPageRequest thePageRequest,
			RequestDetails theRequestDetails,
			String theRequestResourceType) {
		Page resultPage;
		RequestPartitionId readPartitionId =
				myRequestPartitionHelperSvc.determineReadPartitionForRequestForServerOperation(
						theRequestDetails, ProviderConstants.MDM_DUPLICATE_GOLDEN_RESOURCES);

		if (readPartitionId.isAllPartitions()) {
			resultPage = myMdmLinkQuerySvc.getDuplicateGoldenResources(
					theMdmTransactionContext, thePageRequest, null, theRequestResourceType);
		} else {
			resultPage = myMdmLinkQuerySvc.getDuplicateGoldenResources(
					theMdmTransactionContext,
					thePageRequest,
					readPartitionId.getPartitionIds(),
					theRequestResourceType);
		}

		validateMdmQueryPermissions(readPartitionId, resultPage.getContent(), theRequestDetails);

		return resultPage;
	}

	private void convertAndValidateParameters(MdmCreateOrUpdateParams theParams) {
		if (theParams.getGoldenResource() == null) {
			IAnyResource goldenResource = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(
					ProviderConstants.MDM_UPDATE_LINK_GOLDEN_RESOURCE_ID, theParams.getGoldenResourceId());
			theParams.setGoldenResource(goldenResource);
		}
		if (theParams.getSourceResource() == null) {
			IAnyResource source = myMdmControllerHelper.getLatestSourceFromIdOrThrowException(
					ProviderConstants.MDM_UPDATE_LINK_RESOURCE_ID, theParams.getResourceId());
			theParams.setSourceResource(source);
		}
		myMdmControllerHelper.validateSameVersion(theParams.getGoldenResource(), theParams.getGoldenResourceId());
		myMdmControllerHelper.validateSameVersion(theParams.getSourceResource(), theParams.getResourceId());
	}

	@Override
	public IAnyResource updateLink(MdmCreateOrUpdateParams theParams) {
		convertAndValidateParameters(theParams);
		return myIMdmLinkUpdaterSvc.updateLink(theParams);
	}

	@Override
	public IAnyResource createLink(
			String theGoldenResourceId,
			String theSourceResourceId,
			@Nullable String theMatchResult,
			MdmTransactionContext theMdmTransactionContext) {
		MdmCreateOrUpdateParams params = new MdmCreateOrUpdateParams();
		params.setGoldenResourceId(theGoldenResourceId);
		params.setResourceId(theSourceResourceId);
		params.setMdmContext(theMdmTransactionContext);
		if (theMatchResult != null) {
			params.setMatchResult(MdmMatchResultEnum.valueOf(theMatchResult));
		}
		return createLink(params);
	}

	@Override
	public IAnyResource createLink(MdmCreateOrUpdateParams theParams) {
		convertAndValidateParameters(theParams);

		return myIMdmLinkCreateSvc.createLink(theParams);
	}

	@Override
	public IBaseParameters submitMdmClearJob(
			@Nonnull List theResourceNames,
			IPrimitiveType theBatchSize,
			ServletRequestDetails theRequestDetails) {
		MdmClearJobParameters params = new MdmClearJobParameters();
		params.setResourceNames(theResourceNames);
		boolean hasBatchSize = theBatchSize != null
				&& theBatchSize.getValue() != null
				&& theBatchSize.getValue().longValue() > 0;
		if (hasBatchSize) {
			params.setBatchSize(theBatchSize.getValue().intValue());
		}

		RequestPartitionId requestPartition =
				myRequestPartitionHelperSvc.determineReadPartitionForRequestForServerOperation(
						theRequestDetails, ProviderConstants.OPERATION_MDM_CLEAR);
		params.setRequestPartitionId(requestPartition);

		JobInstanceStartRequest request = new JobInstanceStartRequest();
		request.setJobDefinitionId(MdmClearAppCtx.JOB_MDM_CLEAR);
		request.setParameters(params);
		Batch2JobStartResponse response = myJobCoordinator.startInstance(theRequestDetails, request);
		String id = response.getInstanceId();

		if (myInterceptorBroadcaster.hasHooks(Pointcut.MDM_CLEAR)) {
			// MDM_CLEAR hook:
			MdmClearEvent event = new MdmClearEvent();
			event.setResourceTypes(theResourceNames);
			if (hasBatchSize) {
				event.setBatchSize(theBatchSize.getValue().longValue());
			}

			HookParams hookParams = new HookParams();
			hookParams.add(RequestDetails.class, theRequestDetails);
			hookParams.add(MdmClearEvent.class, event);
			myInterceptorBroadcaster.callHooks(Pointcut.MDM_CLEAR, hookParams);
		}

		IBaseParameters retVal = ParametersUtil.newInstance(myFhirContext);
		ParametersUtil.addParameterToParametersString(
				myFhirContext, retVal, ProviderConstants.OPERATION_BATCH_RESPONSE_JOB_ID, id);
		return retVal;
	}

	@Override
	public IBaseParameters submitMdmSubmitJob(
			List theUrls, IPrimitiveType theBatchSize, ServletRequestDetails theRequestDetails) {
		MdmSubmitJobParameters params = new MdmSubmitJobParameters();
		boolean hasBatchSize = theBatchSize != null
				&& theBatchSize.getValue() != null
				&& theBatchSize.getValue().longValue() > 0;
		if (hasBatchSize) {
			params.setBatchSize(theBatchSize.getValue().intValue());
		}
		RequestPartitionId partitionId = RequestPartitionId.allPartitions();
		theUrls.forEach(
				url -> params.addPartitionedUrl(new PartitionedUrl().setUrl(url).setRequestPartitionId(partitionId)));

		JobInstanceStartRequest request = new JobInstanceStartRequest();
		request.setParameters(params);
		request.setJobDefinitionId(MdmSubmitAppCtx.MDM_SUBMIT_JOB);

		Batch2JobStartResponse batch2JobStartResponse = myJobCoordinator.startInstance(theRequestDetails, request);
		String id = batch2JobStartResponse.getInstanceId();

		IBaseParameters retVal = ParametersUtil.newInstance(myFhirContext);
		ParametersUtil.addParameterToParametersString(
				myFhirContext, retVal, ProviderConstants.OPERATION_BATCH_RESPONSE_JOB_ID, id);

		if (myInterceptorBroadcaster.hasHooks(Pointcut.MDM_SUBMIT)) {
			// MDM_SUBMIT batch submit job
			MdmSubmitEvent event = new MdmSubmitEvent();
			event.setBatchJob(true);
			event.setUrls(theUrls);
			if (hasBatchSize) {
				event.setBatchSize(theBatchSize.getValue().longValue());
			}

			HookParams hookParams = new HookParams();
			hookParams.add(RequestDetails.class, theRequestDetails);
			hookParams.add(MdmSubmitEvent.class, event);
			myInterceptorBroadcaster.callHooks(Pointcut.MDM_SUBMIT, hookParams);
		}

		return retVal;
	}

	@Override
	public void notDuplicateGoldenResource(
			String theGoldenResourceId,
			String theTargetGoldenResourceId,
			MdmTransactionContext theMdmTransactionContext) {
		MdmUnduplicateGoldenResourceParams params = new MdmUnduplicateGoldenResourceParams();
		params.setTargetGoldenResourceId(theTargetGoldenResourceId);
		params.setGoldenResourceId(theGoldenResourceId);
		params.setMdmContext(theMdmTransactionContext);

		unduplicateGoldenResource(params);
	}

	@Override
	public void unduplicateGoldenResource(MdmUnduplicateGoldenResourceParams theParams) {
		if (theParams.getGoldenResource() == null) {
			IAnyResource goldenResource = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(
					ProviderConstants.MDM_UPDATE_LINK_GOLDEN_RESOURCE_ID, theParams.getGoldenResourceId());
			theParams.setGoldenResource(goldenResource);
		}
		if (theParams.getTargetGoldenResource() == null) {
			IAnyResource target = myMdmControllerHelper.getLatestGoldenResourceFromIdOrThrowException(
					ProviderConstants.MDM_UPDATE_LINK_RESOURCE_ID, theParams.getTargetGoldenResourceId());
			theParams.setTargetGoldenResource(target);
		}

		myIMdmLinkUpdaterSvc.unduplicateGoldenResource(theParams);
	}

	@Override
	public IAnyResource mergeGoldenResources(
			String theFromGoldenResourceId,
			String theToGoldenResourceId,
			IAnyResource theManuallyMergedGoldenResource,
			MdmTransactionContext theMdmTransactionContext) {
		MdmMergeGoldenResourcesParams params = new MdmMergeGoldenResourcesParams();
		params.setToGoldenResourceId(theToGoldenResourceId);
		params.setFromGoldenResourceId(theFromGoldenResourceId);
		params.setToGoldenResourceId(theToGoldenResourceId);
		params.setManuallyMergedResource(theManuallyMergedGoldenResource);
		params.setMdmTransactionContext(theMdmTransactionContext);
		return mergeGoldenResources(params);
	}

	private void validateMdmQueryPermissions(
			RequestPartitionId theRequestPartitionId,
			List theMdmLinkJsonList,
			RequestDetails theRequestDetails) {
		Set seenResourceTypes = new HashSet<>();
		for (MdmLinkJson mdmLinkJson : theMdmLinkJsonList) {
			IdDt idDt = new IdDt(mdmLinkJson.getSourceId());

			if (!seenResourceTypes.contains(idDt.getResourceType())) {
				myRequestPartitionHelperSvc.validateHasPartitionPermissions(
						theRequestDetails, idDt.getResourceType(), theRequestPartitionId);
				seenResourceTypes.add(idDt.getResourceType());
			}
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy