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