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

ca.uhn.fhir.mdm.interceptor.MdmSearchExpandingInterceptor 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.interceptor;

import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.mdm.api.IMdmLinkExpandSvc;
import ca.uhn.fhir.mdm.log.Logs;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenParam;
import org.hl7.fhir.instance.model.api.IIdType;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * This interceptor replaces the auto-generated CapabilityStatement that is generated
 * by the HAPI FHIR Server with a static hard-coded resource.
 */
@Interceptor
public class MdmSearchExpandingInterceptor {
	// A simple interface to turn ids into some form of IQueryParameterTypes
	private interface Creator {
		T create(String id);
	}

	private static final Logger ourLog = Logs.getMdmTroubleshootingLog();

	@Autowired
	private IRequestPartitionHelperSvc myRequestPartitionHelperSvc;

	@Autowired
	private IMdmLinkExpandSvc myMdmLinkExpandSvc;

	@Autowired
	private JpaStorageSettings myStorageSettings;

	@Hook(Pointcut.STORAGE_PRESEARCH_REGISTERED)
	public void hook(RequestDetails theRequestDetails, SearchParameterMap theSearchParameterMap) {

		if (myStorageSettings.isAllowMdmExpansion()) {
			final RequestDetails requestDetailsToUse =
					theRequestDetails == null ? new SystemRequestDetails() : theRequestDetails;
			final RequestPartitionId requestPartitionId =
					myRequestPartitionHelperSvc.determineReadPartitionForRequestForSearchType(
							requestDetailsToUse, requestDetailsToUse.getResourceName(), theSearchParameterMap);
			for (Map.Entry>> set : theSearchParameterMap.entrySet()) {
				String paramName = set.getKey();
				List> andList = set.getValue();
				for (List orList : andList) {
					// here we will know if it's an _id param or not
					// from theSearchParameterMap.keySet()
					expandAnyReferenceParameters(requestPartitionId, paramName, orList);
				}
			}
		}
	}

	/**
	 * If a Parameter is a reference parameter, and it has been set to expand MDM, perform the expansion.
	 */
	private void expandAnyReferenceParameters(
			RequestPartitionId theRequestPartitionId, String theParamName, List orList) {
		List toRemove = new ArrayList<>();
		List toAdd = new ArrayList<>();
		for (IQueryParameterType iQueryParameterType : orList) {
			if (iQueryParameterType instanceof ReferenceParam) {
				ReferenceParam refParam = (ReferenceParam) iQueryParameterType;
				if (refParam.isMdmExpand()) {
					ourLog.debug("Found a reference parameter to expand: {}", refParam);
					// First, attempt to expand as a source resource.
					Set expandedResourceIds = myMdmLinkExpandSvc.expandMdmBySourceResourceId(
							theRequestPartitionId, new IdDt(refParam.getValue()));

					// If we failed, attempt to expand as a golden resource
					if (expandedResourceIds.isEmpty()) {
						expandedResourceIds = myMdmLinkExpandSvc.expandMdmByGoldenResourceId(
								theRequestPartitionId, new IdDt(refParam.getValue()));
					}

					// Rebuild the search param list.
					if (!expandedResourceIds.isEmpty()) {
						ourLog.debug("Parameter has been expanded to: {}", String.join(", ", expandedResourceIds));
						toRemove.add(refParam);
						expandedResourceIds.stream()
								.map(resourceId -> addResourceTypeIfNecessary(refParam.getResourceType(), resourceId))
								.map(ReferenceParam::new)
								.forEach(toAdd::add);
					}
				}
			} else if (theParamName.equalsIgnoreCase("_id")) {
				expandIdParameter(theRequestPartitionId, iQueryParameterType, toAdd, toRemove);
			}
		}

		orList.removeAll(toRemove);
		orList.addAll(toAdd);
	}

	private String addResourceTypeIfNecessary(String theResourceType, String theResourceId) {
		if (theResourceId.contains("/")) {
			return theResourceId;
		} else {
			return theResourceType + "/" + theResourceId;
		}
	}

	/**
	 * Expands out the provided _id parameter into all the various
	 * ids of linked resources.
	 *
	 * @param theRequestPartitionId
	 * @param theIdParameter
	 * @param theAddList
	 * @param theRemoveList
	 */
	private void expandIdParameter(
			RequestPartitionId theRequestPartitionId,
			IQueryParameterType theIdParameter,
			List theAddList,
			List theRemoveList) {
		// id parameters can either be StringParam (for $everything operation)
		// or TokenParam (for searches)
		// either case, we want to expand it out and grab all related resources
		IIdType id;
		Creator creator;
		boolean mdmExpand = false;
		if (theIdParameter instanceof TokenParam) {
			TokenParam param = (TokenParam) theIdParameter;
			mdmExpand = param.isMdmExpand();
			id = new IdDt(param.getValue());
			creator = TokenParam::new;
		} else {
			creator = null;
			id = null;
		}

		if (id == null) {
			// in case the _id paramter type is different from the above
			ourLog.warn(
					"_id parameter of incorrect type. Expected StringParam or TokenParam, but got {}. No expansion will be done!",
					theIdParameter.getClass().getSimpleName());
		} else if (mdmExpand) {
			ourLog.debug("_id parameter must be expanded out from: {}", id.getValue());

			Set expandedResourceIds = myMdmLinkExpandSvc.expandMdmBySourceResourceId(theRequestPartitionId, id);

			if (expandedResourceIds.isEmpty()) {
				expandedResourceIds = myMdmLinkExpandSvc.expandMdmByGoldenResourceId(theRequestPartitionId, (IdDt) id);
			}

			// Rebuild
			if (!expandedResourceIds.isEmpty()) {
				ourLog.debug("_id parameter has been expanded to: {}", String.join(", ", expandedResourceIds));

				// remove the original
				theRemoveList.add(theIdParameter);

				// add in all the linked values
				expandedResourceIds.stream().map(creator::create).forEach(theAddList::add);
			}
		}
		// else - no expansion required
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy