001/*-
002 * #%L
003 * HAPI FHIR Storage api
004 * %%
005 * Copyright (C) 2014 - 2025 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.jpa.repository;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
024import ca.uhn.fhir.model.api.IQueryParameterAnd;
025import ca.uhn.fhir.model.api.IQueryParameterOr;
026import ca.uhn.fhir.model.api.IQueryParameterType;
027import ca.uhn.fhir.rest.param.TokenOrListParam;
028import ca.uhn.fhir.rest.param.TokenParam;
029import com.google.common.collect.ArrayListMultimap;
030import com.google.common.collect.Multimap;
031import jakarta.annotation.Nonnull;
032
033import java.util.Arrays;
034import java.util.HashMap;
035import java.util.List;
036import java.util.Map;
037
038/**
039 * The IGenericClient API represents searches with OrLists, while the FhirRepository API uses nested
040 * lists. This class (will eventually) convert between them
041 */
042public class SearchConverter {
043        // hardcoded list from FHIR specs: https://www.hl7.org/fhir/search.html
044        private final List<String> mySearchResultParameters = Arrays.asList(
045                        "_sort",
046                        "_count",
047                        "_include",
048                        "_revinclude",
049                        "_summary",
050                        "_total",
051                        "_elements",
052                        "_contained",
053                        "_containedType");
054        public final Multimap<String, List<IQueryParameterType>> mySeparatedSearchParameters = ArrayListMultimap.create();
055        public final Multimap<String, List<IQueryParameterType>> mySeparatedResultParameters = ArrayListMultimap.create();
056        public final SearchParameterMap mySearchParameterMap = new SearchParameterMap();
057        public final Map<String, String[]> myResultParameters = new HashMap<>();
058
059        public void convertParameters(
060                        Multimap<String, List<IQueryParameterType>> theParameters, FhirContext theFhirContext) {
061                if (theParameters == null) {
062                        return;
063                }
064                separateParameterTypes(theParameters);
065                convertToSearchParameterMap(mySeparatedSearchParameters);
066                convertToStringMap(mySeparatedResultParameters, theFhirContext);
067        }
068
069        public void convertToStringMap(
070                        @Nonnull Multimap<String, List<IQueryParameterType>> theParameters, @Nonnull FhirContext theFhirContext) {
071                for (Map.Entry<String, List<IQueryParameterType>> entry : theParameters.entries()) {
072                        String[] values = new String[entry.getValue().size()];
073                        for (int i = 0; i < entry.getValue().size(); i++) {
074                                values[i] = entry.getValue().get(i).getValueAsQueryToken(theFhirContext);
075                        }
076                        myResultParameters.put(entry.getKey(), values);
077                }
078        }
079
080        public void convertToSearchParameterMap(Multimap<String, List<IQueryParameterType>> theSearchMap) {
081                if (theSearchMap == null) {
082                        return;
083                }
084                for (Map.Entry<String, List<IQueryParameterType>> entry : theSearchMap.entries()) {
085                        // if list of parameters is the value
086                        if (entry.getValue().size() > 1 && !isOrList(entry.getValue()) && !isAndList(entry.getValue())) {
087                                // is value a TokenParam
088                                addTokenToSearchIfNeeded(entry);
089
090                                // parameter type is single value list
091                        } else {
092                                for (IQueryParameterType value : entry.getValue()) {
093                                        setParameterTypeValue(entry.getKey(), value);
094                                }
095                        }
096                }
097        }
098
099        private void addTokenToSearchIfNeeded(Map.Entry<String, List<IQueryParameterType>> theEntry) {
100                if (isTokenParam(theEntry.getValue().get(0))) {
101                        String tokenKey = theEntry.getKey();
102                        TokenOrListParam tokenList = new TokenOrListParam();
103                        for (IQueryParameterType rec : theEntry.getValue()) {
104                                tokenList.add((TokenParam) rec);
105                        }
106                        mySearchParameterMap.add(tokenKey, tokenList);
107                }
108        }
109
110        public <T> void setParameterTypeValue(@Nonnull String theKey, @Nonnull T theParameterType) {
111                if (isOrList(theParameterType)) {
112                        mySearchParameterMap.add(theKey, (IQueryParameterOr<?>) theParameterType);
113                } else if (isAndList(theParameterType)) {
114                        mySearchParameterMap.add(theKey, (IQueryParameterAnd<?>) theParameterType);
115                } else {
116                        mySearchParameterMap.add(theKey, (IQueryParameterType) theParameterType);
117                }
118        }
119
120        public void separateParameterTypes(@Nonnull Multimap<String, List<IQueryParameterType>> theParameters) {
121                for (Map.Entry<String, List<IQueryParameterType>> entry : theParameters.entries()) {
122                        if (isSearchResultParameter(entry.getKey())) {
123                                mySeparatedResultParameters.put(entry.getKey(), entry.getValue());
124                        } else {
125                                mySeparatedSearchParameters.put(entry.getKey(), entry.getValue());
126                        }
127                }
128        }
129
130        public boolean isSearchResultParameter(String theParameterName) {
131                return mySearchResultParameters.contains(theParameterName);
132        }
133
134        public <T> boolean isOrList(@Nonnull T theParameterType) {
135                return IQueryParameterOr.class.isAssignableFrom(theParameterType.getClass());
136        }
137
138        public <T> boolean isAndList(@Nonnull T theParameterType) {
139                return IQueryParameterAnd.class.isAssignableFrom(theParameterType.getClass());
140        }
141
142        public <T> boolean isTokenParam(@Nonnull T theParameterType) {
143                return TokenParam.class.isAssignableFrom(theParameterType.getClass());
144        }
145}