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

ca.uhn.fhir.mdm.svc.MdmSurvivorshipSvcImpl Maven / Gradle / Ivy

/*-
 * #%L
 * HAPI FHIR - 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.mdm.svc;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
import ca.uhn.fhir.mdm.api.IMdmLinkQuerySvc;
import ca.uhn.fhir.mdm.api.IMdmSurvivorshipService;
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
import ca.uhn.fhir.mdm.api.paging.MdmPageRequest;
import ca.uhn.fhir.mdm.api.params.MdmQuerySearchParameters;
import ca.uhn.fhir.mdm.model.MdmTransactionContext;
import ca.uhn.fhir.mdm.model.mdmevents.MdmLinkJson;
import ca.uhn.fhir.mdm.util.GoldenResourceHelper;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
import ca.uhn.fhir.util.TerserUtil;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.data.domain.Page;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Stream;

public class MdmSurvivorshipSvcImpl implements IMdmSurvivorshipService {
	private static final Pattern IS_UUID =
			Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}");

	protected final FhirContext myFhirContext;

	private final GoldenResourceHelper myGoldenResourceHelper;

	private final DaoRegistry myDaoRegistry;
	private final IMdmLinkQuerySvc myMdmLinkQuerySvc;

	private final IIdHelperService myIIdHelperService;

	private final HapiTransactionService myTransactionService;

	public MdmSurvivorshipSvcImpl(
			FhirContext theFhirContext,
			GoldenResourceHelper theResourceHelper,
			DaoRegistry theDaoRegistry,
			IMdmLinkQuerySvc theLinkQuerySvc,
			IIdHelperService theIIdHelperService,
			HapiTransactionService theHapiTransactionService) {
		myFhirContext = theFhirContext;
		myGoldenResourceHelper = theResourceHelper;
		myDaoRegistry = theDaoRegistry;
		myMdmLinkQuerySvc = theLinkQuerySvc;
		myIIdHelperService = theIIdHelperService;
		myTransactionService = theHapiTransactionService;
	}

	// this logic is custom in smile vs hapi
	@Override
	public  void applySurvivorshipRulesToGoldenResource(
			T theTargetResource, T theGoldenResource, MdmTransactionContext theMdmTransactionContext) {
		switch (theMdmTransactionContext.getRestOperation()) {
			case MERGE_GOLDEN_RESOURCES:
				TerserUtil.mergeFields(
						myFhirContext,
						(IBaseResource) theTargetResource,
						(IBaseResource) theGoldenResource,
						TerserUtil.EXCLUDE_IDS_AND_META);
				break;
			default:
				TerserUtil.replaceFields(
						myFhirContext,
						(IBaseResource) theTargetResource,
						(IBaseResource) theGoldenResource,
						TerserUtil.EXCLUDE_IDS_AND_META);
				break;
		}
	}

	// This logic is the same for all implementations (including jpa or mongo)
	@SuppressWarnings({"rawtypes", "unchecked"})
	@Override
	public  T rebuildGoldenResourceWithSurvivorshipRules(
			T theGoldenResourceBase, MdmTransactionContext theMdmTransactionContext) {
		IBaseResource goldenResource = (IBaseResource) theGoldenResourceBase;

		// we want a list of source ids linked to this
		// golden resource id; sorted and filtered for only MATCH results
		Stream sourceResources =
				getMatchedSourceIdsByLinkUpdateDate(goldenResource, theMdmTransactionContext);

		IBaseResource toSave = myGoldenResourceHelper.createGoldenResourceFromMdmSourceResource(
				(IAnyResource) goldenResource,
				theMdmTransactionContext,
				null // we don't want to apply survivorship - just create a new GoldenResource
				);

		toSave.setId(goldenResource.getIdElement().toUnqualifiedVersionless());

		sourceResources.forEach(source -> {
			applySurvivorshipRulesToGoldenResource(source, toSave, theMdmTransactionContext);
		});

		// save it
		IFhirResourceDao dao = myDaoRegistry.getResourceDao(goldenResource.fhirType());

		SystemRequestDetails requestDetails = new SystemRequestDetails();
		// if using partitions, we should save to the correct partition
		Object resourcePartitionIdObj = toSave.getUserData(Constants.RESOURCE_PARTITION_ID);
		if (resourcePartitionIdObj instanceof RequestPartitionId) {
			RequestPartitionId partitionId = (RequestPartitionId) resourcePartitionIdObj;
			requestDetails.setRequestPartitionId(partitionId);
		}
		dao.update(toSave, requestDetails);

		return (T) toSave;
	}

	@SuppressWarnings("rawtypes")
	private Stream getMatchedSourceIdsByLinkUpdateDate(
			IBaseResource theGoldenResource, MdmTransactionContext theMdmTransactionContext) {
		String resourceType = theGoldenResource.fhirType();
		IFhirResourceDao dao = myDaoRegistry.getResourceDao(resourceType);

		MdmQuerySearchParameters searchParameters = new MdmQuerySearchParameters(new MdmPageRequest(0, 50, 50, 50));
		searchParameters.setGoldenResourceId(theGoldenResource.getIdElement());
		searchParameters.setSort("myUpdated");
		searchParameters.setMatchResult(MdmMatchResultEnum.MATCH);
		Page linksQuery = myMdmLinkQuerySvc.queryLinks(searchParameters, theMdmTransactionContext);

		// we want it ordered
		List sourceIds = new ArrayList<>();
		linksQuery.forEach(link -> {
			String sourceId = link.getSourceId();
			// we want only the id part, not the resource type
			sourceId = sourceId.replace(resourceType + "/", "");
			sourceIds.add(sourceId);
		});
		Map sourceIdToPid = new HashMap<>();
		if (!sourceIds.isEmpty()) {
			// we cannot call resolveResourcePersistentIds if there are no ids to call it with
			myTransactionService
					.withRequest(new SystemRequestDetails().setRequestPartitionId(RequestPartitionId.allPartitions()))
					.execute(() -> {
						Map ids =
								myIIdHelperService.resolveResourcePersistentIds(
										RequestPartitionId.allPartitions(), resourceType, sourceIds);
						sourceIdToPid.putAll(ids);
					});
		}

		return sourceIds.stream().map(id -> {
			IResourcePersistentId pid = sourceIdToPid.get(id);
			return dao.readByPid(pid);
		});
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy