001/*
002 * #%L
003 * HAPI FHIR - Server Framework
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.rest.server.util;
021
022import ca.uhn.fhir.context.ComboSearchParamType;
023import ca.uhn.fhir.context.RuntimeSearchParam;
024import ca.uhn.fhir.context.phonetic.IPhoneticEncoder;
025import ca.uhn.fhir.i18n.Msg;
026import ca.uhn.fhir.rest.api.Constants;
027import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
028import jakarta.annotation.Nonnull;
029import jakarta.annotation.Nullable;
030import org.hl7.fhir.instance.model.api.IAnyResource;
031import org.hl7.fhir.instance.model.api.IIdType;
032
033import java.util.Collection;
034import java.util.Collections;
035import java.util.List;
036import java.util.Optional;
037import java.util.Set;
038import java.util.TreeSet;
039
040// TODO: JA remove default methods
041public interface ISearchParamRegistry extends IResourceRepositoryCache {
042
043        /**
044         * Return true if this registry is initialized and ready to handle
045         * searches and use its cache.
046         * Return false if cache has not been initialized.
047         */
048        default boolean isInitialized() {
049                // default initialized to not break current implementers
050                return true;
051        }
052
053        /**
054         * @deprecated Use {@link #getActiveSearchParam(String, String, SearchParamLookupContextEnum)}
055         */
056        @Deprecated(since = "8.0.0", forRemoval = true)
057        default RuntimeSearchParam getActiveSearchParam(String theResourceName, String theParamName) {
058                return getActiveSearchParam(theResourceName, theParamName, SearchParamLookupContextEnum.ALL);
059        }
060
061        /**
062         * @param theContext The context to return active search params for, or {@literal null} to return any active search params
063         * @return Returns {@literal null} if no match
064         */
065        RuntimeSearchParam getActiveSearchParam(
066                        @Nonnull String theResourceName,
067                        @Nonnull String theParamName,
068                        @Nonnull SearchParamLookupContextEnum theContext);
069
070        /**
071         * @deprecated Use {@link #getActiveSearchParam(String, String, SearchParamLookupContextEnum)}
072         */
073        @Deprecated(since = "8.0.0", forRemoval = true)
074        default ResourceSearchParams getActiveSearchParams(String theResourceName) {
075                return getActiveSearchParams(theResourceName, SearchParamLookupContextEnum.ALL);
076        }
077
078        /**
079         * @param theContext The context to return active search params for, or {@literal null} to return any active search params
080         * @return Returns all active search params for the given resource
081         */
082        ResourceSearchParams getActiveSearchParams(
083                        @Nonnull String theResourceName, @Nonnull SearchParamLookupContextEnum theContext);
084
085        /**
086         * When indexing a HumanName, if a StringEncoder is set in the context, then the "phonetic" search parameter will normalize
087         * the String using this encoder.
088         *
089         * @since 5.1.0
090         */
091        default void setPhoneticEncoder(IPhoneticEncoder thePhoneticEncoder) {}
092
093        /**
094         * @param theContext The context to return active search params for, or {@literal null} to return any active search params
095         */
096        default List<RuntimeSearchParam> getActiveComboSearchParams(
097                        @Nonnull String theResourceName, @Nonnull SearchParamLookupContextEnum theContext) {
098                return Collections.emptyList();
099        }
100
101        // TODO ND remove default implementation
102        default List<RuntimeSearchParam> getActiveComboSearchParams(
103                        @Nonnull String theResourceName,
104                        @Nonnull ComboSearchParamType theParamType,
105                        @Nonnull SearchParamLookupContextEnum theContext) {
106                return Collections.emptyList();
107        }
108
109        // TODO ND remove default implementation
110        default Optional<RuntimeSearchParam> getActiveComboSearchParamById(
111                        @Nonnull String theResourceName, @Nonnull IIdType theId) {
112                return Optional.empty();
113        }
114
115        /**
116         * @param theContext The context to return active search params for, or {@literal null} to return any active search params
117         */
118        default List<RuntimeSearchParam> getActiveComboSearchParams(
119                        @Nonnull String theResourceName,
120                        @Nonnull Set<String> theParamNames,
121                        @Nonnull SearchParamLookupContextEnum theContext) {
122                return Collections.emptyList();
123        }
124
125        /**
126         * Returns a collection containing all of the valid active search parameters. This method is intended for
127         * creating error messages for users as opposed to actual search processing. It will include meta parameters
128         * such as <code>_id</code> and <code>_lastUpdated</code>.
129         *
130         * @param theContext The context to return active search params for, or {@literal null} to return any active search params
131         */
132        default Collection<String> getValidSearchParameterNamesIncludingMeta(
133                        @Nonnull String theResourceName, @Nonnull SearchParamLookupContextEnum theContext) {
134                TreeSet<String> retval;
135                ResourceSearchParams activeSearchParams = getActiveSearchParams(theResourceName, theContext);
136                if (activeSearchParams == null) {
137                        retval = new TreeSet<>();
138                } else {
139                        retval = new TreeSet<>(activeSearchParams.getSearchParamNames());
140                }
141                retval.add(IAnyResource.SP_RES_ID);
142                retval.add(Constants.PARAM_LASTUPDATED);
143                return retval;
144        }
145
146        /**
147         * Fetch a SearchParameter by URL
148         *
149         * @param theContext The context to return active search params for, or {@literal null} to return any active search params
150         * @return Returns <code>null</code> if it can't be found
151         */
152        @Nullable
153        RuntimeSearchParam getActiveSearchParamByUrl(
154                        @Nonnull String theUrl, @Nonnull SearchParamLookupContextEnum theContext);
155
156        /**
157         * Find a search param for a resource. First, check the resource itself, then check the top-level `Resource` resource.
158         *
159         * @param theResourceType the resource type.
160         * @param theParamName the search parameter name.
161         * @param theContext The context to return active search params for, or {@literal null} to return any active search params
162         * @return the {@link RuntimeSearchParam} that is found.
163         */
164        default RuntimeSearchParam getRuntimeSearchParam(
165                        @Nonnull String theResourceType,
166                        @Nonnull String theParamName,
167                        @Nonnull SearchParamLookupContextEnum theContext) {
168                RuntimeSearchParam availableSearchParamDef = getActiveSearchParam(theResourceType, theParamName, theContext);
169                if (availableSearchParamDef == null) {
170                        availableSearchParamDef = getActiveSearchParam("Resource", theParamName, theContext);
171                }
172                if (availableSearchParamDef == null) {
173                        throw new InvalidRequestException(
174                                        Msg.code(1209) + "Unknown parameter name: " + theResourceType + ':' + theParamName);
175                }
176                return availableSearchParamDef;
177        }
178
179        /**
180         * Get all the search params for a resource. First, check the resource itself, then check the top-level `Resource` resource and combine the two.
181         *
182         * @param theContext The context to return active search params for, or {@literal null} to return any active search params
183         * @param theResourceType the resource type.
184         * @return the {@link ResourceSearchParams} that has all the search params.
185         */
186        default ResourceSearchParams getRuntimeSearchParams(
187                        @Nonnull String theResourceType, @Nonnull SearchParamLookupContextEnum theContext) {
188                ResourceSearchParams availableSearchParams =
189                                getActiveSearchParams(theResourceType, theContext).makeCopy();
190                ResourceSearchParams resourceSearchParams = getActiveSearchParams("Resource", theContext);
191                resourceSearchParams
192                                .getSearchParamNames()
193                                .forEach(param -> availableSearchParams.addSearchParamIfAbsent(param, resourceSearchParams.get(param)));
194                return availableSearchParams;
195        }
196
197        /**
198         * Describes the context for looking up individual search parameters or lists of search parameters.
199         * These can be thought of as filter criteria - Most search parameters generally apply to all
200         * context, but some may be explicitly defined to only work for some.
201         *
202         * @since 8.0.0
203         */
204        enum SearchParamLookupContextEnum {
205                /**
206                 * Search parameter should be used when indexing a resource that is being persisted
207                 */
208                INDEX,
209                /**
210                 * Search parameter should be used for searching. This includes explicit searches such as
211                 * standard REST FHIR searches, but also includes resolving match URLs, subscription criteria,
212                 * etc.
213                 */
214                SEARCH,
215                /**
216                 * Search parameter should be used for sorting via the {@literal _sort} parameter.
217                 */
218                SORT,
219                /**
220                 * Return any search parameters that are known to the system for any context
221                 */
222                ALL
223        }
224
225        static boolean isAllowedForContext(
226                        @Nonnull RuntimeSearchParam theSearchParam, @Nullable SearchParamLookupContextEnum theContext) {
227                /*
228                 * I'm thinking that a future enhancement might be to allow a SearchParameter to declare that it
229                 * is supported for searching or for sorting or for both - But for now these are one and the same.
230                 */
231                if (theContext == SearchParamLookupContextEnum.SEARCH || theContext == SearchParamLookupContextEnum.SORT) {
232                        return theSearchParam.isEnabledForSearching();
233                }
234                return true;
235        }
236}