
001/* 002 * #%L 003 * HAPI FHIR JPA - Search Parameters 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.searchparam.registry; 021 022import ca.uhn.fhir.context.ComboSearchParamType; 023import ca.uhn.fhir.context.FhirContext; 024import ca.uhn.fhir.context.RuntimeSearchParam; 025import ca.uhn.fhir.context.phonetic.IPhoneticEncoder; 026import ca.uhn.fhir.i18n.Msg; 027import ca.uhn.fhir.interceptor.api.IInterceptorService; 028import ca.uhn.fhir.jpa.cache.IResourceChangeEvent; 029import ca.uhn.fhir.jpa.cache.IResourceChangeListener; 030import ca.uhn.fhir.jpa.cache.IResourceChangeListenerCache; 031import ca.uhn.fhir.jpa.cache.IResourceChangeListenerRegistry; 032import ca.uhn.fhir.jpa.cache.ISearchParamIdentityCacheSvc; 033import ca.uhn.fhir.jpa.cache.ResourceChangeResult; 034import ca.uhn.fhir.jpa.model.config.PartitionSettings; 035import ca.uhn.fhir.jpa.model.entity.StorageSettings; 036import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 037import ca.uhn.fhir.rest.api.Constants; 038import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; 039import ca.uhn.fhir.rest.api.server.IBundleProvider; 040import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; 041import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; 042import ca.uhn.fhir.rest.server.util.IndexedSearchParam; 043import ca.uhn.fhir.rest.server.util.ResourceSearchParams; 044import ca.uhn.fhir.util.SearchParameterUtil; 045import ca.uhn.fhir.util.StopWatch; 046import com.google.common.annotations.VisibleForTesting; 047import com.google.common.collect.Sets; 048import jakarta.annotation.Nonnull; 049import jakarta.annotation.Nullable; 050import jakarta.annotation.PostConstruct; 051import jakarta.annotation.PreDestroy; 052import org.apache.commons.lang3.StringUtils; 053import org.apache.commons.lang3.time.DateUtils; 054import org.hl7.fhir.instance.model.api.IBaseResource; 055import org.hl7.fhir.instance.model.api.IIdType; 056import org.slf4j.Logger; 057import org.slf4j.LoggerFactory; 058import org.springframework.beans.factory.ObjectProvider; 059import org.springframework.beans.factory.annotation.Autowired; 060 061import java.util.ArrayList; 062import java.util.Collection; 063import java.util.Collections; 064import java.util.HashSet; 065import java.util.Iterator; 066import java.util.List; 067import java.util.Map; 068import java.util.Objects; 069import java.util.Optional; 070import java.util.Set; 071import java.util.stream.Collectors; 072 073import static ca.uhn.fhir.rest.server.util.ISearchParamRegistry.isAllowedForContext; 074import static org.apache.commons.lang3.StringUtils.isNotBlank; 075 076public class SearchParamRegistryImpl 077 implements ISearchParamRegistry, IResourceChangeListener, ISearchParamRegistryController { 078 079 // Basic is needed by the R4 SubscriptionTopic registry 080 public static final Set<String> NON_DISABLEABLE_SEARCH_PARAMS = 081 Collections.unmodifiableSet(Sets.newHashSet("*:url", "Subscription:*", "SearchParameter:*", "Basic:*")); 082 083 private static final Logger ourLog = LoggerFactory.getLogger(SearchParamRegistryImpl.class); 084 public static final int MAX_MANAGED_PARAM_COUNT = 10000; 085 private static final long REFRESH_INTERVAL = DateUtils.MILLIS_PER_MINUTE; 086 public static final String PARAM_LANGUAGE_ID = "SearchParameter/Resource-language"; 087 public static final String PARAM_LANGUAGE_DESCRIPTION = "Language of the resource content"; 088 public static final String PARAM_LANGUAGE_PATH = "language"; 089 public static final String PARAM_TEXT_DESCRIPTION = "Text search against the narrative"; 090 public static final String PARAM_CONTENT_DESCRIPTION = "Search on the entire content of the resource"; 091 092 private JpaSearchParamCache myJpaSearchParamCache; 093 094 @Autowired 095 private StorageSettings myStorageSettings; 096 097 @Autowired 098 private ISearchParamProvider mySearchParamProvider; 099 100 @Autowired 101 private FhirContext myFhirContext; 102 103 @Autowired 104 private SearchParameterCanonicalizer mySearchParameterCanonicalizer; 105 106 @Autowired 107 private IInterceptorService myInterceptorBroadcaster; 108 109 @Autowired 110 private IResourceChangeListenerRegistry myResourceChangeListenerRegistry; 111 112 @Autowired 113 private PartitionSettings myPartitionSettings; 114 115 @Autowired 116 private ObjectProvider<ISearchParamIdentityCacheSvc> mySearchParamIdentityCacheSvcProvider; 117 118 private IResourceChangeListenerCache myResourceChangeListenerCache; 119 private volatile ReadOnlySearchParamCache myBuiltInSearchParams; 120 private volatile IPhoneticEncoder myPhoneticEncoder; 121 private volatile RuntimeSearchParamCache myActiveSearchParams; 122 private boolean myPrePopulateSearchParamIdentities = true; 123 124 @VisibleForTesting 125 public void setPopulateSearchParamIdentities(boolean myPrePopulateSearchParamIdentities) { 126 this.myPrePopulateSearchParamIdentities = myPrePopulateSearchParamIdentities; 127 } 128 129 /** 130 * Constructor 131 */ 132 public SearchParamRegistryImpl() { 133 super(); 134 } 135 136 @PostConstruct 137 public void start() { 138 myJpaSearchParamCache = new JpaSearchParamCache(myPartitionSettings); 139 } 140 141 @Override 142 public RuntimeSearchParam getActiveSearchParam( 143 @Nonnull String theResourceName, 144 @Nonnull String theParamName, 145 @Nonnull SearchParamLookupContextEnum theContext) { 146 requiresActiveSearchParams(); 147 148 // Can still be null in unit test scenarios 149 if (myActiveSearchParams != null) { 150 RuntimeSearchParam param = myActiveSearchParams.get(theResourceName, theParamName); 151 if (param != null) { 152 if (isAllowedForContext(param, theContext)) { 153 return param; 154 } 155 } 156 } 157 158 return null; 159 } 160 161 @Nonnull 162 @Override 163 public ResourceSearchParams getActiveSearchParams( 164 @Nonnull String theResourceName, @Nonnull SearchParamLookupContextEnum theContext) { 165 requiresActiveSearchParams(); 166 return getActiveSearchParams().getSearchParamMap(theResourceName).toFilteredForContext(theContext); 167 } 168 169 private void requiresActiveSearchParams() { 170 if (myActiveSearchParams == null) { 171 // forced refreshes should not use a cache - we're forcibly refreshing it, after all 172 myResourceChangeListenerCache.forceRefresh(); 173 } 174 } 175 176 @Override 177 public List<RuntimeSearchParam> getActiveComboSearchParams( 178 @Nonnull String theResourceName, @Nonnull SearchParamLookupContextEnum theContext) { 179 return filteredForContext(myJpaSearchParamCache.getActiveComboSearchParams(theResourceName), theContext); 180 } 181 182 @Override 183 public List<RuntimeSearchParam> getActiveComboSearchParams( 184 @Nonnull String theResourceName, 185 @Nonnull ComboSearchParamType theParamType, 186 @Nonnull SearchParamLookupContextEnum theContext) { 187 return filteredForContext( 188 myJpaSearchParamCache.getActiveComboSearchParams(theResourceName, theParamType), theContext); 189 } 190 191 @Override 192 public List<RuntimeSearchParam> getActiveComboSearchParams( 193 @Nonnull String theResourceName, 194 @Nonnull Set<String> theParamNames, 195 @Nonnull SearchParamLookupContextEnum theContext) { 196 return filteredForContext( 197 myJpaSearchParamCache.getActiveComboSearchParams(theResourceName, theParamNames), theContext); 198 } 199 200 @Nullable 201 @Override 202 public RuntimeSearchParam getActiveSearchParamByUrl( 203 @Nonnull String theUrl, @Nonnull SearchParamLookupContextEnum theContext) { 204 if (myActiveSearchParams != null) { 205 RuntimeSearchParam param = myActiveSearchParams.getByUrl(theUrl); 206 if (isAllowedForContext(param, theContext)) { 207 return param; 208 } 209 } 210 return null; 211 } 212 213 @Override 214 public Optional<RuntimeSearchParam> getActiveComboSearchParamById( 215 @Nonnull String theResourceName, @Nonnull IIdType theId) { 216 return myJpaSearchParamCache.getActiveComboSearchParamById(theResourceName, theId); 217 } 218 219 private void rebuildActiveSearchParams() { 220 ourLog.info("Rebuilding SearchParamRegistry"); 221 SearchParameterMap params = new SearchParameterMap(); 222 params.setLoadSynchronousUpTo(MAX_MANAGED_PARAM_COUNT); 223 params.setCount(MAX_MANAGED_PARAM_COUNT); 224 225 IBundleProvider allSearchParamsBp = mySearchParamProvider.search(params); 226 227 List<IBaseResource> allSearchParams = allSearchParamsBp.getResources(0, MAX_MANAGED_PARAM_COUNT); 228 Integer size = allSearchParamsBp.size(); 229 230 ourLog.trace("Loaded {} search params from the DB", allSearchParams.size()); 231 232 if (size == null) { 233 ourLog.error( 234 "Only {} search parameters have been loaded, but there are more than that in the repository. Is offset search configured on this server?", 235 allSearchParams.size()); 236 } else if (size >= MAX_MANAGED_PARAM_COUNT) { 237 ourLog.warn("Unable to support >" + MAX_MANAGED_PARAM_COUNT + " search params!"); 238 } 239 240 initializeActiveSearchParams(allSearchParams); 241 } 242 243 private void initializeActiveSearchParams(Collection<IBaseResource> theJpaSearchParams) { 244 StopWatch sw = new StopWatch(); 245 246 ReadOnlySearchParamCache builtInSearchParams = getBuiltInSearchParams(); 247 RuntimeSearchParamCache searchParams = 248 RuntimeSearchParamCache.fromReadOnlySearchParamCache(builtInSearchParams); 249 long overriddenCount = overrideBuiltinSearchParamsWithActiveJpaSearchParams(searchParams, theJpaSearchParams); 250 ourLog.trace("Have overridden {} built-in search parameters", overriddenCount); 251 252 // Auto-register: _language 253 if (myStorageSettings.isLanguageSearchParameterEnabled()) { 254 registerImplicitSearchParam( 255 searchParams, 256 Constants.PARAM_LANGUAGE_URL, 257 Constants.PARAM_LANGUAGE, 258 PARAM_LANGUAGE_DESCRIPTION, 259 PARAM_LANGUAGE_PATH, 260 RestSearchParameterTypeEnum.TOKEN); 261 } else { 262 unregisterImplicitSearchParam(searchParams, Constants.PARAM_LANGUAGE); 263 } 264 265 // Auto-register: _content and _text 266 if (myStorageSettings.isHibernateSearchIndexFullText()) { 267 registerImplicitSearchParam( 268 searchParams, 269 Constants.PARAM_TEXT_URL, 270 Constants.PARAM_TEXT, 271 PARAM_TEXT_DESCRIPTION, 272 "Resource", 273 RestSearchParameterTypeEnum.STRING); 274 registerImplicitSearchParam( 275 searchParams, 276 Constants.PARAM_CONTENT_URL, 277 Constants.PARAM_CONTENT, 278 PARAM_CONTENT_DESCRIPTION, 279 "Resource", 280 RestSearchParameterTypeEnum.STRING); 281 } else { 282 unregisterImplicitSearchParam(searchParams, Constants.PARAM_CONTENT); 283 unregisterImplicitSearchParam(searchParams, Constants.PARAM_TEXT); 284 } 285 286 removeInactiveSearchParams(searchParams); 287 288 setActiveSearchParams(searchParams); 289 290 myJpaSearchParamCache.populateActiveSearchParams( 291 myInterceptorBroadcaster, myPhoneticEncoder, myActiveSearchParams); 292 updateSearchParameterIdentityCache(); 293 ourLog.debug("Refreshed search parameter cache in {}ms", sw.getMillis()); 294 } 295 296 private void unregisterImplicitSearchParam(RuntimeSearchParamCache theSearchParams, String theParamName) { 297 for (String resourceType : theSearchParams.getResourceNameKeys()) { 298 theSearchParams.remove(resourceType, theParamName); 299 } 300 } 301 302 private void registerImplicitSearchParam( 303 RuntimeSearchParamCache searchParams, 304 String url, 305 String code, 306 String description, 307 String path, 308 RestSearchParameterTypeEnum type) { 309 if (searchParams.getByUrl(url) == null) { 310 RuntimeSearchParam sp = new RuntimeSearchParam( 311 myFhirContext.getVersion().newIdType(PARAM_LANGUAGE_ID), 312 url, 313 code, 314 description, 315 path, 316 type, 317 Collections.emptySet(), 318 Collections.emptySet(), 319 RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE, 320 myFhirContext.getResourceTypes()); 321 for (String baseResourceType : sp.getBase()) { 322 searchParams.add(baseResourceType, sp.getName(), sp); 323 } 324 } 325 } 326 327 private void updateSearchParameterIdentityCache() { 328 if (!myPrePopulateSearchParamIdentities) { 329 return; 330 } 331 332 ISearchParamIdentityCacheSvc spIdentityCacheSvc = mySearchParamIdentityCacheSvcProvider.getIfAvailable(); 333 if (spIdentityCacheSvc == null) { 334 return; 335 } 336 337 myJpaSearchParamCache 338 .getHashIdentityToIndexedSearchParamMap() 339 .forEach((hash, param) -> spIdentityCacheSvc.findOrCreateSearchParamIdentity( 340 hash, param.getResourceType(), param.getParameterName())); 341 } 342 343 @VisibleForTesting 344 public Map<Long, IndexedSearchParam> getHashIdentityToIndexedSearchParamMap() { 345 return myJpaSearchParamCache.getHashIdentityToIndexedSearchParamMap(); 346 } 347 348 @VisibleForTesting 349 public void setFhirContext(FhirContext theFhirContext) { 350 myFhirContext = theFhirContext; 351 } 352 353 private ReadOnlySearchParamCache getBuiltInSearchParams() { 354 if (myBuiltInSearchParams == null) { 355 if (myStorageSettings.isAutoSupportDefaultSearchParams()) { 356 myBuiltInSearchParams = 357 ReadOnlySearchParamCache.fromFhirContext(myFhirContext, mySearchParameterCanonicalizer); 358 } else { 359 // Only the built-in search params that can not be disabled will be supported automatically 360 myBuiltInSearchParams = ReadOnlySearchParamCache.fromFhirContext( 361 myFhirContext, mySearchParameterCanonicalizer, NON_DISABLEABLE_SEARCH_PARAMS); 362 } 363 } 364 return myBuiltInSearchParams; 365 } 366 367 private void removeInactiveSearchParams(RuntimeSearchParamCache theSearchParams) { 368 for (String resourceName : theSearchParams.getResourceNameKeys()) { 369 ResourceSearchParams resourceSearchParams = theSearchParams.getSearchParamMap(resourceName); 370 resourceSearchParams.removeInactive(); 371 } 372 } 373 374 @VisibleForTesting 375 public void setStorageSettings(StorageSettings theStorageSettings) { 376 myStorageSettings = theStorageSettings; 377 } 378 379 private long overrideBuiltinSearchParamsWithActiveJpaSearchParams( 380 RuntimeSearchParamCache theSearchParamCache, Collection<IBaseResource> theSearchParams) { 381 if (!myStorageSettings.isDefaultSearchParamsCanBeOverridden() || theSearchParams == null) { 382 return 0; 383 } 384 385 long retval = 0; 386 for (IBaseResource searchParam : theSearchParams) { 387 retval += overrideSearchParam(theSearchParamCache, searchParam); 388 } 389 return retval; 390 } 391 392 /** 393 * For the given SearchParameter which was fetched from the database, look for any 394 * existing search parameters in the cache that should be replaced by the SP (i.e. 395 * because they represent the same parameter) 396 * 397 * @param theSearchParams The cache to populate 398 * @param theSearchParameter The SearchParameter to insert into the cache and potentially replace existing params 399 */ 400 private long overrideSearchParam(RuntimeSearchParamCache theSearchParams, IBaseResource theSearchParameter) { 401 if (theSearchParameter == null) { 402 return 0; 403 } 404 405 RuntimeSearchParam runtimeSp = mySearchParameterCanonicalizer.canonicalizeSearchParameter(theSearchParameter); 406 if (runtimeSp == null) { 407 return 0; 408 } 409 410 /* 411 * This check means that we basically ignore SPs from the database if they have a status 412 * of "draft". I don't know that this makes sense, but it has worked this way for a long 413 * time and changing it could potentially screw with people who didn't realize they 414 * were depending on this behaviour? I don't know.. Honestly this is probably being 415 * overly cautious. -JA 416 */ 417 if (runtimeSp.getStatus() == RuntimeSearchParam.RuntimeSearchParamStatusEnum.DRAFT) { 418 return 0; 419 } 420 421 /* 422 * If an SP in the cache has the same URL as the one we are inserting, first remove 423 * the old SP from anywhere it is registered. This helps us override SPs like _content 424 * and _text. 425 */ 426 String url = runtimeSp.getUri(); 427 RuntimeSearchParam existingParam = theSearchParams.getByUrl(url); 428 if (existingParam != null) { 429 if (isNotBlank(existingParam.getName()) && !existingParam.getName().equals(runtimeSp.getName())) { 430 ourLog.warn( 431 "Existing SearchParameter with URL[{}] and name[{}] doesn't match name[{}] found on SearchParameter: {}", 432 url, 433 existingParam.getName(), 434 runtimeSp.getName(), 435 runtimeSp.getId()); 436 } else { 437 Set<String> expandedBases = expandBaseList(existingParam.getBase()); 438 for (String base : expandedBases) { 439 theSearchParams.remove(base, existingParam.getName()); 440 } 441 } 442 } 443 444 long retval = 0; 445 for (String nextBaseName : 446 expandBaseList(SearchParameterUtil.getBaseAsStrings(myFhirContext, theSearchParameter))) { 447 String name = runtimeSp.getName(); 448 theSearchParams.add(nextBaseName, name, runtimeSp); 449 ourLog.debug( 450 "Adding search parameter {}.{} to SearchParamRegistry", 451 nextBaseName, 452 Objects.toString(name, "[composite]")); 453 retval++; 454 } 455 return retval; 456 } 457 458 private @Nonnull Set<String> expandBaseList(Collection<String> nextBase) { 459 Set<String> expandedBases = new HashSet<>(); 460 for (String base : nextBase) { 461 if ("Resource".equals(base) || "DomainResource".equals(base)) { 462 expandedBases.addAll(myFhirContext.getResourceTypes()); 463 break; 464 } else { 465 expandedBases.add(base); 466 } 467 } 468 return expandedBases; 469 } 470 471 @Override 472 public void requestRefresh() { 473 myResourceChangeListenerCache.requestRefresh(); 474 } 475 476 @Override 477 public void forceRefresh() { 478 RuntimeSearchParamCache activeSearchParams = myActiveSearchParams; 479 myResourceChangeListenerCache.forceRefresh(); 480 481 // If the refresh didn't trigger a change, proceed with one anyway 482 if (myActiveSearchParams == activeSearchParams) { 483 rebuildActiveSearchParams(); 484 } 485 } 486 487 @Override 488 public ResourceChangeResult refreshCacheIfNecessary() { 489 return myResourceChangeListenerCache.refreshCacheIfNecessary(); 490 } 491 492 @VisibleForTesting 493 public void setResourceChangeListenerRegistry(IResourceChangeListenerRegistry theResourceChangeListenerRegistry) { 494 myResourceChangeListenerRegistry = theResourceChangeListenerRegistry; 495 } 496 497 /** 498 * There is a circular reference between this class and the ResourceChangeListenerRegistry: 499 * SearchParamRegistryImpl -> ResourceChangeListenerRegistry -> InMemoryResourceMatcher -> SearchParamRegistryImpl. Since we only need this once on boot-up, we delay 500 * until ContextRefreshedEvent. 501 */ 502 @PostConstruct 503 public void registerListener() { 504 SearchParameterMap spMap = SearchParameterMap.newSynchronous(); 505 spMap.setLoadSynchronousUpTo(MAX_MANAGED_PARAM_COUNT); 506 myResourceChangeListenerCache = myResourceChangeListenerRegistry.registerResourceResourceChangeListener( 507 "SearchParameter", spMap, this, REFRESH_INTERVAL); 508 } 509 510 @PreDestroy 511 public void unregisterListener() { 512 myResourceChangeListenerRegistry.unregisterResourceResourceChangeListener(this); 513 } 514 515 public ReadOnlySearchParamCache getActiveSearchParams() { 516 requiresActiveSearchParams(); 517 if (myActiveSearchParams == null) { 518 throw new IllegalStateException(Msg.code(511) + "SearchParamRegistry has not been initialized"); 519 } 520 return ReadOnlySearchParamCache.fromRuntimeSearchParamCache(myActiveSearchParams); 521 } 522 523 @VisibleForTesting 524 public void setActiveSearchParams(RuntimeSearchParamCache theSearchParams) { 525 myActiveSearchParams = theSearchParams; 526 } 527 528 /** 529 * All SearchParameters with the name "phonetic" encode the normalized index value using this phonetic encoder. 530 * 531 * @since 5.1.0 532 */ 533 @Override 534 public void setPhoneticEncoder(IPhoneticEncoder thePhoneticEncoder) { 535 myPhoneticEncoder = thePhoneticEncoder; 536 537 if (myActiveSearchParams == null) { 538 return; 539 } 540 myActiveSearchParams 541 .getSearchParamStream() 542 .forEach(searchParam -> myJpaSearchParamCache.setPhoneticEncoder(myPhoneticEncoder, searchParam)); 543 } 544 545 @Override 546 public void handleChange(IResourceChangeEvent theResourceChangeEvent) { 547 if (theResourceChangeEvent.isEmpty()) { 548 return; 549 } 550 551 ResourceChangeResult result = ResourceChangeResult.fromResourceChangeEvent(theResourceChangeEvent); 552 if (result.created > 0) { 553 ourLog.info( 554 "Adding {} search parameters to SearchParamRegistry: {}", 555 result.created, 556 unqualified(theResourceChangeEvent.getCreatedResourceIds())); 557 } 558 if (result.updated > 0) { 559 ourLog.info( 560 "Updating {} search parameters in SearchParamRegistry: {}", 561 result.updated, 562 unqualified(theResourceChangeEvent.getUpdatedResourceIds())); 563 } 564 if (result.deleted > 0) { 565 ourLog.info( 566 "Deleting {} search parameters from SearchParamRegistry: {}", 567 result.deleted, 568 unqualified(theResourceChangeEvent.getDeletedResourceIds())); 569 } 570 rebuildActiveSearchParams(); 571 } 572 573 private String unqualified(List<IIdType> theIds) { 574 Iterator<String> unqualifiedIds = theIds.stream() 575 .map(IIdType::toUnqualifiedVersionless) 576 .map(IIdType::getValue) 577 .iterator(); 578 579 return StringUtils.join(unqualifiedIds, ", "); 580 } 581 582 @Override 583 public void handleInit(Collection<IIdType> theResourceIds) { 584 List<IBaseResource> searchParams = new ArrayList<>(); 585 for (IIdType id : theResourceIds) { 586 try { 587 IBaseResource searchParam = mySearchParamProvider.read(id); 588 searchParams.add(searchParam); 589 } catch (ResourceNotFoundException e) { 590 ourLog.warn("SearchParameter {} not found. Excluding from list of active search params.", id); 591 } 592 } 593 initializeActiveSearchParams(searchParams); 594 } 595 596 @Override 597 public boolean isInitialized() { 598 return myActiveSearchParams != null; 599 } 600 601 @VisibleForTesting 602 public void resetForUnitTest() { 603 myBuiltInSearchParams = null; 604 setActiveSearchParams(null); 605 handleInit(Collections.emptyList()); 606 } 607 608 @VisibleForTesting 609 public void setSearchParameterCanonicalizerForUnitTest( 610 SearchParameterCanonicalizer theSearchParameterCanonicalizerForUnitTest) { 611 mySearchParameterCanonicalizer = theSearchParameterCanonicalizerForUnitTest; 612 } 613 614 private static List<RuntimeSearchParam> filteredForContext( 615 List<RuntimeSearchParam> theActiveComboSearchParams, SearchParamLookupContextEnum theContext) { 616 return theActiveComboSearchParams.stream() 617 .filter(t -> isAllowedForContext(t, theContext)) 618 .collect(Collectors.toList()); 619 } 620}