
001package ca.uhn.fhir.jpa.interceptor; 002 003/*- 004 * #%L 005 * HAPI FHIR JPA Server 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.interceptor.api.Hook; 024import ca.uhn.fhir.interceptor.api.Interceptor; 025import ca.uhn.fhir.interceptor.api.Pointcut; 026import ca.uhn.fhir.jpa.api.config.DaoConfig; 027import ca.uhn.fhir.jpa.dao.mdm.MdmLinkExpandSvc; 028import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 029import ca.uhn.fhir.mdm.log.Logs; 030import ca.uhn.fhir.model.api.IQueryParameterType; 031import ca.uhn.fhir.model.primitive.IdDt; 032import ca.uhn.fhir.rest.param.ReferenceParam; 033import ca.uhn.fhir.rest.param.TokenParam; 034import org.hl7.fhir.instance.model.api.IIdType; 035import org.slf4j.Logger; 036import org.springframework.beans.factory.annotation.Autowired; 037 038import java.util.ArrayList; 039import java.util.List; 040import java.util.Map; 041import java.util.Set; 042 043/** 044 * This interceptor replaces the auto-generated CapabilityStatement that is generated 045 * by the HAPI FHIR Server with a static hard-coded resource. 046 */ 047@Interceptor 048public class MdmSearchExpandingInterceptor { 049 // A simple interface to turn ids into some form of IQueryParameterTypes 050 private interface Creator<T extends IQueryParameterType> { 051 T create(String id); 052 } 053 054 private static final Logger ourLog = Logs.getMdmTroubleshootingLog(); 055 056 @Autowired 057 private MdmLinkExpandSvc myMdmLinkExpandSvc; 058 059 @Autowired 060 private DaoConfig myDaoConfig; 061 062 @Hook(Pointcut.STORAGE_PRESEARCH_REGISTERED) 063 public void hook(SearchParameterMap theSearchParameterMap) { 064 if (myDaoConfig.isAllowMdmExpansion()) { 065 for (Map.Entry<String, List<List<IQueryParameterType>>> set : theSearchParameterMap.entrySet()) { 066 String paramName = set.getKey(); 067 List<List<IQueryParameterType>> andList = set.getValue(); 068 for (List<IQueryParameterType> orList : andList) { 069 // here we will know if it's an _id param or not 070 // from theSearchParameterMap.keySet() 071 expandAnyReferenceParameters(paramName, orList); 072 } 073 } 074 } 075 } 076 077 /** 078 * If a Parameter is a reference parameter, and it has been set to expand MDM, perform the expansion. 079 */ 080 private void expandAnyReferenceParameters(String theParamName, List<IQueryParameterType> orList) { 081 List<IQueryParameterType> toRemove = new ArrayList<>(); 082 List<IQueryParameterType> toAdd = new ArrayList<>(); 083 for (IQueryParameterType iQueryParameterType : orList) { 084 if (iQueryParameterType instanceof ReferenceParam) { 085 ReferenceParam refParam = (ReferenceParam) iQueryParameterType; 086 if (refParam.isMdmExpand()) { 087 ourLog.debug("Found a reference parameter to expand: {}", refParam); 088 //First, attempt to expand as a source resource. 089 Set<String> expandedResourceIds = myMdmLinkExpandSvc.expandMdmBySourceResourceId(new IdDt(refParam.getValue())); 090 091 // If we failed, attempt to expand as a golden resource 092 if (expandedResourceIds.isEmpty()) { 093 expandedResourceIds = myMdmLinkExpandSvc.expandMdmByGoldenResourceId(new IdDt(refParam.getValue())); 094 } 095 096 //Rebuild the search param list. 097 if (!expandedResourceIds.isEmpty()) { 098 ourLog.debug("Parameter has been expanded to: {}", String.join(", ", expandedResourceIds)); 099 toRemove.add(refParam); 100 expandedResourceIds.stream() 101 .map(resourceId -> new ReferenceParam(refParam.getResourceType() + "/" + resourceId)) 102 .forEach(toAdd::add); 103 } 104 } 105 } else if (theParamName.equalsIgnoreCase("_id")) { 106 expandIdParameter(iQueryParameterType, toAdd, toRemove); 107 } 108 } 109 110 orList.removeAll(toRemove); 111 orList.addAll(toAdd); 112 } 113 114 /** 115 * Expands out the provided _id parameter into all the various 116 * ids of linked resources. 117 * 118 * @param theIdParameter 119 * @param theAddList 120 * @param theRemoveList 121 */ 122 private void expandIdParameter(IQueryParameterType theIdParameter, 123 List<IQueryParameterType> theAddList, 124 List<IQueryParameterType> theRemoveList) { 125 // id parameters can either be StringParam (for $everything operation) 126 // or TokenParam (for searches) 127 // either case, we want to expand it out and grab all related resources 128 IIdType id; 129 Creator<? extends IQueryParameterType> creator; 130 boolean mdmExpand = false; 131 if (theIdParameter instanceof TokenParam) { 132 TokenParam param = (TokenParam) theIdParameter; 133 mdmExpand = param.isMdmExpand(); 134 id = new IdDt(param.getValue()); 135 creator = TokenParam::new; 136 } else { 137 creator = null; 138 id = null; 139 } 140 141 if (id == null) { 142 // in case the _id paramter type is different from the above 143 ourLog.warn("_id parameter of incorrect type. Expected StringParam or TokenParam, but got {}. No expansion will be done!", 144 theIdParameter.getClass().getSimpleName()); 145 } else if (mdmExpand) { 146 ourLog.debug("_id parameter must be expanded out from: {}", id.getValue()); 147 148 Set<String> expandedResourceIds = myMdmLinkExpandSvc.expandMdmBySourceResourceId(id); 149 150 if (expandedResourceIds.isEmpty()) { 151 expandedResourceIds = myMdmLinkExpandSvc.expandMdmByGoldenResourceId(id.getIdPartAsLong()); 152 } 153 154 //Rebuild 155 if (!expandedResourceIds.isEmpty()) { 156 ourLog.debug("_id parameter has been expanded to: {}", String.join(", ", expandedResourceIds)); 157 158 // remove the original 159 theRemoveList.add(theIdParameter); 160 161 // add in all the linked values 162 expandedResourceIds.stream() 163 .map(creator::create) 164 .forEach(theAddList::add); 165 } 166 } 167 // else - no expansion required 168 } 169}