
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}