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}