
001/*- 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2026 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.repository.impl; 021 022import ca.uhn.fhir.i18n.Msg; 023import ca.uhn.fhir.model.api.IQueryParameterType; 024import ca.uhn.fhir.repository.IRepository.IRepositoryRestQueryContributor; 025import ca.uhn.fhir.repository.IRepositoryRestQueryBuilder; 026import ca.uhn.fhir.rest.param.ParameterUtil; 027import com.google.common.collect.ArrayListMultimap; 028import com.google.common.collect.Lists; 029import com.google.common.collect.Multimap; 030import jakarta.annotation.Nonnull; 031 032import java.util.Collection; 033import java.util.HashMap; 034import java.util.List; 035import java.util.Map; 036 037import static java.util.Objects.requireNonNull; 038 039/** 040 * This class provides a rest-query builder over a plain Multimap. 041 * It is used to convert {@link IRepositoryRestQueryContributor} implementations 042 * that are not Multimap-based so they can be used by IRepository implementations that are. 043 */ 044public class MultiMapRepositoryRestQueryBuilder implements IRepositoryRestQueryBuilder { 045 /** 046 * Our search parameters. 047 * We use a list multimap to maintain insertion order, and because most of IQueryParameterType don't 048 * provide a meaningful equals/hashCode implementation. 049 */ 050 private final Multimap<String, List<IQueryParameterType>> mySearchParameters = ArrayListMultimap.create(); 051 052 @Nonnull 053 public static Map<String, String[]> toFlatMap(IRepositoryRestQueryContributor searchParameterMap) { 054 MultiMapRepositoryRestQueryBuilder builder = new MultiMapRepositoryRestQueryBuilder(); 055 searchParameterMap.contributeToQuery(builder); 056 Multimap<String, List<IQueryParameterType>> m = builder.toMultiMap(); 057 return flattenMultimap(m); 058 } 059 060 /** 061 * Converts a Multimap of search parameters to a flat Map of Strings. 062 */ 063 @Nonnull 064 static Map<String, String[]> flattenMultimap(Multimap<String, List<IQueryParameterType>> theQueryMap) { 065 Map<String, String[]> result = new HashMap<>(); 066 067 theQueryMap.asMap().forEach((key, value) -> result.put(key, flattenValues(value))); 068 069 return result; 070 } 071 072 /** 073 * Flatten the and/or lists of IQueryParameterType into a String array. 074 */ 075 private static @Nonnull String[] flattenValues(Collection<List<IQueryParameterType>> theOrLists) { 076 // hacky - this ignores modifiers. Those should probably move back to the keys for this legacy api. 077 // But this is a dead api. 078 return theOrLists.stream() 079 .map(MultiMapRepositoryRestQueryBuilder::flattenOrList) 080 .toArray(String[]::new); 081 } 082 083 /** Build an or-list string */ 084 private static String flattenOrList(List<IQueryParameterType> theOrList) { 085 return ParameterUtil.escapeAndJoinOrList( 086 Lists.transform(theOrList, p -> requireNonNull(p).getValueAsQueryToken())); 087 } 088 089 @Override 090 public IRepositoryRestQueryBuilder addOrList(String theParamName, List<IQueryParameterType> theParameters) { 091 validateHomogeneousList(theParamName, theParameters); 092 mySearchParameters.put(theParamName, theParameters); 093 return this; 094 } 095 096 private void validateHomogeneousList(String theName, List<IQueryParameterType> theValues) { 097 if (theValues.isEmpty()) { 098 return; 099 } 100 IQueryParameterType firstValue = theValues.get(0); 101 for (IQueryParameterType nextValue : theValues) { 102 if (!nextValue.getClass().equals(firstValue.getClass())) { 103 throw new IllegalArgumentException( 104 Msg.code(2833) + "All parameters in an or-list must be of the same type. Found " 105 + firstValue.getClass().getSimpleName() + " and " 106 + nextValue.getClass().getSimpleName() + " in parameter '" + theName + "'"); 107 } 108 } 109 } 110 111 public Multimap<String, List<IQueryParameterType>> toMultiMap() { 112 return mySearchParameters; 113 } 114 115 /** 116 * Converts a {@link IRepositoryRestQueryContributor} to a Multimap. 117 * 118 * @param theSearchQueryBuilder the contributor to convert 119 * @return a Multimap containing the search parameters contributed by the contributor 120 */ 121 public static Multimap<String, List<IQueryParameterType>> contributorToMultimap( 122 IRepositoryRestQueryContributor theSearchQueryBuilder) { 123 MultiMapRepositoryRestQueryBuilder builder = new MultiMapRepositoryRestQueryBuilder(); 124 theSearchQueryBuilder.contributeToQuery(builder); 125 return builder.toMultiMap(); 126 } 127}