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