001/*
002 * #%L
003 * HAPI FHIR - Server Framework
004 * %%
005 * Copyright (C) 2014 - 2024 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 {
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         * Request that the cache be refreshed now, in the current thread
087         */
088        default void forceRefresh() {}
089
090        /**
091         * Request that the cache be refreshed at the next convenient time (in a different thread)
092         */
093        default void requestRefresh() {}
094
095        /**
096         * When indexing a HumanName, if a StringEncoder is set in the context, then the "phonetic" search parameter will normalize
097         * the String using this encoder.
098         *
099         * @since 5.1.0
100         */
101        default void setPhoneticEncoder(IPhoneticEncoder thePhoneticEncoder) {}
102
103        /**
104         * @param theContext The context to return active search params for, or {@literal null} to return any active search params
105         */
106        default List<RuntimeSearchParam> getActiveComboSearchParams(
107                        @Nonnull String theResourceName, @Nonnull SearchParamLookupContextEnum theContext) {
108                return Collections.emptyList();
109        }
110
111        // TODO ND remove default implementation
112        default List<RuntimeSearchParam> getActiveComboSearchParams(
113                        @Nonnull String theResourceName,
114                        @Nonnull ComboSearchParamType theParamType,
115                        @Nonnull SearchParamLookupContextEnum theContext) {
116                return Collections.emptyList();
117        }
118
119        // TODO ND remove default implementation
120        default Optional<RuntimeSearchParam> getActiveComboSearchParamById(
121                        @Nonnull String theResourceName, @Nonnull IIdType theId) {
122                return Optional.empty();
123        }
124
125        /**
126         * @param theContext The context to return active search params for, or {@literal null} to return any active search params
127         */
128        default List<RuntimeSearchParam> getActiveComboSearchParams(
129                        @Nonnull String theResourceName,
130                        @Nonnull Set<String> theParamNames,
131                        @Nonnull SearchParamLookupContextEnum theContext) {
132                return Collections.emptyList();
133        }
134
135        /**
136         * Returns a collection containing all of the valid active search parameters. This method is intended for
137         * creating error messages for users as opposed to actual search processing. It will include meta parameters
138         * such as <code>_id</code> and <code>_lastUpdated</code>.
139         *
140         * @param theContext The context to return active search params for, or {@literal null} to return any active search params
141         */
142        default Collection<String> getValidSearchParameterNamesIncludingMeta(
143                        @Nonnull String theResourceName, @Nonnull SearchParamLookupContextEnum theContext) {
144                TreeSet<String> retval;
145                ResourceSearchParams activeSearchParams = getActiveSearchParams(theResourceName, theContext);
146                if (activeSearchParams == null) {
147                        retval = new TreeSet<>();
148                } else {
149                        retval = new TreeSet<>(activeSearchParams.getSearchParamNames());
150                }
151                retval.add(IAnyResource.SP_RES_ID);
152                retval.add(Constants.PARAM_LASTUPDATED);
153                return retval;
154        }
155
156        /**
157         * Fetch a SearchParameter by URL
158         *
159         * @param theContext The context to return active search params for, or {@literal null} to return any active search params
160         * @return Returns <code>null</code> if it can't be found
161         */
162        @Nullable
163        RuntimeSearchParam getActiveSearchParamByUrl(
164                        @Nonnull String theUrl, @Nonnull SearchParamLookupContextEnum theContext);
165
166        /**
167         * Find a search param for a resource. First, check the resource itself, then check the top-level `Resource` resource.
168         *
169         * @param theResourceType the resource type.
170         * @param theParamName the search parameter name.
171         * @param theContext The context to return active search params for, or {@literal null} to return any active search params
172         * @return the {@link RuntimeSearchParam} that is found.
173         */
174        default RuntimeSearchParam getRuntimeSearchParam(
175                        @Nonnull String theResourceType,
176                        @Nonnull String theParamName,
177                        @Nonnull SearchParamLookupContextEnum theContext) {
178                RuntimeSearchParam availableSearchParamDef = getActiveSearchParam(theResourceType, theParamName, theContext);
179                if (availableSearchParamDef == null) {
180                        availableSearchParamDef = getActiveSearchParam("Resource", theParamName, theContext);
181                }
182                if (availableSearchParamDef == null) {
183                        throw new InvalidRequestException(
184                                        Msg.code(1209) + "Unknown parameter name: " + theResourceType + ':' + theParamName);
185                }
186                return availableSearchParamDef;
187        }
188
189        /**
190         * Get all the search params for a resource. First, check the resource itself, then check the top-level `Resource` resource and combine the two.
191         *
192         * @param theContext The context to return active search params for, or {@literal null} to return any active search params
193         * @param theResourceType the resource type.
194         * @return the {@link ResourceSearchParams} that has all the search params.
195         */
196        default ResourceSearchParams getRuntimeSearchParams(
197                        @Nonnull String theResourceType, @Nonnull SearchParamLookupContextEnum theContext) {
198                ResourceSearchParams availableSearchParams =
199                                getActiveSearchParams(theResourceType, theContext).makeCopy();
200                ResourceSearchParams resourceSearchParams = getActiveSearchParams("Resource", theContext);
201                resourceSearchParams
202                                .getSearchParamNames()
203                                .forEach(param -> availableSearchParams.addSearchParamIfAbsent(param, resourceSearchParams.get(param)));
204                return availableSearchParams;
205        }
206
207        /**
208         * Describes the context for looking up individual search parameters or lists of search parameters.
209         * These can be thought of as filter criteria - Most search parameters generally apply to all
210         * context, but some may be explicitly defined to only work for some.
211         *
212         * @since 8.0.0
213         */
214        enum SearchParamLookupContextEnum {
215                /**
216                 * Search parameter should be used when indexing a resource that is being persisted
217                 */
218                INDEX,
219                /**
220                 * Search parameter should be used for searching. This includes explicit searches such as
221                 * standard REST FHIR searches, but also includes resolving match URLs, subscription criteria,
222                 * etc.
223                 */
224                SEARCH,
225                /**
226                 * Search parameter should be used for sorting via the {@literal _sort} parameter.
227                 */
228                SORT,
229                /**
230                 * Return any search parameters that are known to the system for any context
231                 */
232                ALL
233        }
234
235        static boolean isAllowedForContext(
236                        @Nonnull RuntimeSearchParam theSearchParam, @Nullable SearchParamLookupContextEnum theContext) {
237                /*
238                 * I'm thinking that a future enhancement might be to allow a SearchParameter to declare that it
239                 * is supported for searching or for sorting or for both - But for now these are one and the same.
240                 */
241                if (theContext == SearchParamLookupContextEnum.SEARCH || theContext == SearchParamLookupContextEnum.SORT) {
242                        return theSearchParam.isEnabledForSearching();
243                }
244                return true;
245        }
246}