
001/*- 002 * #%L 003 * HAPI FHIR Search Parameters 004 * %% 005 * Copyright (C) 2014 - 2023 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.extractor; 021 022import ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.context.FhirVersionEnum; 024import ca.uhn.fhir.context.RuntimeResourceDefinition; 025import ca.uhn.fhir.context.RuntimeSearchParam; 026import ca.uhn.fhir.i18n.Msg; 027import ca.uhn.fhir.interceptor.api.HookParams; 028import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; 029import ca.uhn.fhir.interceptor.api.Pointcut; 030import ca.uhn.fhir.interceptor.model.RequestPartitionId; 031import ca.uhn.fhir.jpa.model.config.PartitionSettings; 032import ca.uhn.fhir.jpa.model.cross.IResourceLookup; 033import ca.uhn.fhir.jpa.model.dao.JpaPid; 034import ca.uhn.fhir.jpa.model.entity.BasePartitionable; 035import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; 036import ca.uhn.fhir.jpa.model.entity.IResourceIndexComboSearchParameter; 037import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel; 038import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique; 039import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique; 040import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords; 041import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; 042import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber; 043import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; 044import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized; 045import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; 046import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; 047import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri; 048import ca.uhn.fhir.jpa.model.entity.ResourceLink; 049import ca.uhn.fhir.jpa.model.entity.ResourceTable; 050import ca.uhn.fhir.jpa.model.entity.SearchParamPresentEntity; 051import ca.uhn.fhir.jpa.model.entity.StorageSettings; 052import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; 053import ca.uhn.fhir.parser.DataFormatException; 054import ca.uhn.fhir.rest.api.server.RequestDetails; 055import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; 056import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 057import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; 058import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster; 059import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; 060import ca.uhn.fhir.rest.server.util.ResourceSearchParams; 061import ca.uhn.fhir.util.FhirTerser; 062import com.google.common.annotations.VisibleForTesting; 063import org.apache.commons.lang3.StringUtils; 064import org.hl7.fhir.instance.model.api.IBaseReference; 065import org.hl7.fhir.instance.model.api.IBaseResource; 066import org.hl7.fhir.instance.model.api.IIdType; 067import org.hl7.fhir.r4.model.IdType; 068import org.springframework.beans.factory.annotation.Autowired; 069 070import java.util.ArrayList; 071import java.util.Collection; 072import java.util.Date; 073import java.util.HashMap; 074import java.util.HashSet; 075import java.util.List; 076import java.util.Map; 077import java.util.Optional; 078import java.util.Set; 079import java.util.stream.Collectors; 080import javax.annotation.Nonnull; 081import javax.annotation.Nullable; 082 083import static org.apache.commons.lang3.StringUtils.isBlank; 084import static org.apache.commons.lang3.StringUtils.isNotBlank; 085 086public class SearchParamExtractorService { 087 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParamExtractorService.class); 088 089 @Autowired 090 private ISearchParamExtractor mySearchParamExtractor; 091 092 @Autowired 093 private IInterceptorBroadcaster myInterceptorBroadcaster; 094 095 @Autowired 096 private StorageSettings myStorageSettings; 097 098 @Autowired 099 private FhirContext myContext; 100 101 @Autowired 102 private ISearchParamRegistry mySearchParamRegistry; 103 104 @Autowired 105 private PartitionSettings myPartitionSettings; 106 107 @Autowired(required = false) 108 private IResourceLinkResolver myResourceLinkResolver; 109 110 @VisibleForTesting 111 public void setSearchParamExtractor(ISearchParamExtractor theSearchParamExtractor) { 112 mySearchParamExtractor = theSearchParamExtractor; 113 } 114 115 public void extractFromResource( 116 RequestPartitionId theRequestPartitionId, 117 RequestDetails theRequestDetails, 118 ResourceIndexedSearchParams theParams, 119 ResourceTable theEntity, 120 IBaseResource theResource, 121 TransactionDetails theTransactionDetails, 122 boolean theFailOnInvalidReference) { 123 extractFromResource( 124 theRequestPartitionId, 125 theRequestDetails, 126 theParams, 127 new ResourceIndexedSearchParams(), 128 theEntity, 129 theResource, 130 theTransactionDetails, 131 theFailOnInvalidReference, 132 ISearchParamExtractor.ALL_PARAMS); 133 } 134 135 /** 136 * This method is responsible for scanning a resource for all of the search parameter instances. 137 * I.e. for all search parameters defined for 138 * a given resource type, it extracts the associated indexes and populates 139 * {@literal theParams}. 140 */ 141 public void extractFromResource( 142 RequestPartitionId theRequestPartitionId, 143 RequestDetails theRequestDetails, 144 ResourceIndexedSearchParams theNewParams, 145 ResourceIndexedSearchParams theExistingParams, 146 ResourceTable theEntity, 147 IBaseResource theResource, 148 TransactionDetails theTransactionDetails, 149 boolean theFailOnInvalidReference, 150 @Nonnull ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { 151 // All search parameter types except Reference 152 ResourceIndexedSearchParams normalParams = new ResourceIndexedSearchParams(); 153 extractSearchIndexParameters(theRequestDetails, normalParams, theResource, theSearchParamFilter); 154 mergeParams(normalParams, theNewParams); 155 156 boolean indexOnContainedResources = myStorageSettings.isIndexOnContainedResources(); 157 ISearchParamExtractor.SearchParamSet<PathAndRef> indexedReferences = 158 mySearchParamExtractor.extractResourceLinks(theResource, indexOnContainedResources); 159 SearchParamExtractorService.handleWarnings(theRequestDetails, myInterceptorBroadcaster, indexedReferences); 160 161 if (indexOnContainedResources) { 162 ResourceIndexedSearchParams containedParams = new ResourceIndexedSearchParams(); 163 extractSearchIndexParametersForContainedResources( 164 theRequestDetails, containedParams, theResource, theEntity, indexedReferences); 165 mergeParams(containedParams, theNewParams); 166 } 167 168 if (myStorageSettings.isIndexOnUpliftedRefchains()) { 169 ResourceIndexedSearchParams containedParams = new ResourceIndexedSearchParams(); 170 extractSearchIndexParametersForUpliftedRefchains( 171 theRequestDetails, 172 containedParams, 173 theEntity, 174 theRequestPartitionId, 175 theTransactionDetails, 176 indexedReferences); 177 mergeParams(containedParams, theNewParams); 178 } 179 180 // Do this after, because we add to strings during both string and token processing, and contained resource if 181 // any 182 populateResourceTables(theNewParams, theEntity); 183 184 // Reference search parameters 185 extractResourceLinks( 186 theRequestPartitionId, 187 theExistingParams, 188 theNewParams, 189 theEntity, 190 theResource, 191 theTransactionDetails, 192 theFailOnInvalidReference, 193 theRequestDetails, 194 indexedReferences); 195 196 if (indexOnContainedResources) { 197 extractResourceLinksForContainedResources( 198 theRequestPartitionId, 199 theNewParams, 200 theEntity, 201 theResource, 202 theTransactionDetails, 203 theFailOnInvalidReference, 204 theRequestDetails); 205 } 206 207 // Missing (:missing) Indexes - These are indexes to satisfy the :missing 208 // modifier 209 if (myStorageSettings.getIndexMissingFields() == StorageSettings.IndexEnabledEnum.ENABLED) { 210 211 // References 212 Map<String, Boolean> presenceMap = getReferenceSearchParamPresenceMap(theEntity, theNewParams); 213 presenceMap.forEach((key, value) -> { 214 SearchParamPresentEntity present = new SearchParamPresentEntity(); 215 present.setPartitionSettings(myPartitionSettings); 216 present.setResource(theEntity); 217 present.setParamName(key); 218 present.setPresent(value); 219 present.setPartitionId(theEntity.getPartitionId()); 220 present.calculateHashes(); 221 theNewParams.mySearchParamPresentEntities.add(present); 222 }); 223 224 // Everything else 225 ResourceSearchParams activeSearchParams = 226 mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()); 227 theNewParams.findMissingSearchParams(myPartitionSettings, myStorageSettings, theEntity, activeSearchParams); 228 } 229 230 extractSearchParamComboUnique(theEntity, theNewParams); 231 232 extractSearchParamComboNonUnique(theEntity, theNewParams); 233 234 theNewParams.setUpdatedTime(theTransactionDetails.getTransactionDate()); 235 } 236 237 @Nonnull 238 private Map<String, Boolean> getReferenceSearchParamPresenceMap( 239 ResourceTable entity, ResourceIndexedSearchParams newParams) { 240 Map<String, Boolean> retval = new HashMap<>(); 241 242 for (String nextKey : newParams.getPopulatedResourceLinkParameters()) { 243 retval.put(nextKey, Boolean.TRUE); 244 } 245 246 ResourceSearchParams activeSearchParams = mySearchParamRegistry.getActiveSearchParams(entity.getResourceType()); 247 activeSearchParams.getReferenceSearchParamNames().forEach(key -> retval.putIfAbsent(key, Boolean.FALSE)); 248 return retval; 249 } 250 251 @VisibleForTesting 252 public void setStorageSettings(StorageSettings theStorageSettings) { 253 myStorageSettings = theStorageSettings; 254 } 255 256 /** 257 * Extract search parameter indexes for contained resources. E.g. if we 258 * are storing a Patient with a contained Organization, we might extract 259 * a String index on the Patient with paramName="organization.name" and 260 * value="Org Name" 261 */ 262 private void extractSearchIndexParametersForContainedResources( 263 RequestDetails theRequestDetails, 264 ResourceIndexedSearchParams theParams, 265 IBaseResource theResource, 266 ResourceTable theEntity, 267 ISearchParamExtractor.SearchParamSet<PathAndRef> theIndexedReferences) { 268 269 FhirTerser terser = myContext.newTerser(); 270 271 // 1. get all contained resources 272 Collection<IBaseResource> containedResources = terser.getAllEmbeddedResources(theResource, false); 273 274 // Extract search parameters 275 IChainedSearchParameterExtractionStrategy strategy = new IChainedSearchParameterExtractionStrategy() { 276 @Nonnull 277 @Override 278 public ISearchParamExtractor.ISearchParamFilter getSearchParamFilter(@Nonnull PathAndRef thePathAndRef) { 279 // Currently for contained resources we always index all search parameters 280 // on all contained resources. A potential nice future optimization would 281 // be to make this configurable, perhaps with an optional extension you could 282 // add to a SearchParameter? 283 return ISearchParamExtractor.ALL_PARAMS; 284 } 285 286 @Override 287 public IBaseResource fetchResourceAtPath(@Nonnull PathAndRef thePathAndRef) { 288 if (thePathAndRef.getRef() == null) { 289 return null; 290 } 291 return findContainedResource(containedResources, thePathAndRef.getRef()); 292 } 293 }; 294 boolean recurse = myStorageSettings.isIndexOnContainedResourcesRecursively(); 295 extractSearchIndexParametersForTargetResources( 296 theRequestDetails, 297 theParams, 298 theEntity, 299 new HashSet<>(), 300 strategy, 301 theIndexedReferences, 302 recurse, 303 true); 304 } 305 306 /** 307 * Extract search parameter indexes for uplifted refchains. E.g. if we 308 * are storing a Patient with reference to an Organization and the 309 * "Patient:organization" SearchParameter declares an uplifted refchain 310 * on the "name" SearchParameter, we might extract a String index 311 * on the Patient with paramName="organization.name" and value="Org Name" 312 */ 313 private void extractSearchIndexParametersForUpliftedRefchains( 314 RequestDetails theRequestDetails, 315 ResourceIndexedSearchParams theParams, 316 ResourceTable theEntity, 317 RequestPartitionId theRequestPartitionId, 318 TransactionDetails theTransactionDetails, 319 ISearchParamExtractor.SearchParamSet<PathAndRef> theIndexedReferences) { 320 IChainedSearchParameterExtractionStrategy strategy = new IChainedSearchParameterExtractionStrategy() { 321 322 @Nonnull 323 @Override 324 public ISearchParamExtractor.ISearchParamFilter getSearchParamFilter(@Nonnull PathAndRef thePathAndRef) { 325 String searchParamName = thePathAndRef.getSearchParamName(); 326 RuntimeSearchParam searchParam = 327 mySearchParamRegistry.getActiveSearchParam(theEntity.getResourceType(), searchParamName); 328 Set<String> upliftRefchainCodes = searchParam.getUpliftRefchainCodes(); 329 if (upliftRefchainCodes.isEmpty()) { 330 return ISearchParamExtractor.NO_PARAMS; 331 } 332 return sp -> sp.stream() 333 .filter(t -> upliftRefchainCodes.contains(t.getName())) 334 .collect(Collectors.toList()); 335 } 336 337 @Override 338 public IBaseResource fetchResourceAtPath(@Nonnull PathAndRef thePathAndRef) { 339 // The PathAndRef will contain a resource if the SP path was inside a Bundle 340 // and pointed to a resource (e.g. Bundle.entry.resource) as opposed to 341 // pointing to a reference (e.g. Observation.subject) 342 if (thePathAndRef.getResource() != null) { 343 return thePathAndRef.getResource(); 344 } 345 346 // Ok, it's a normal reference 347 IIdType reference = thePathAndRef.getRef().getReferenceElement(); 348 349 // If we're processing a FHIR transaction, we store the resources 350 // mapped by their resolved resource IDs in theTransactionDetails 351 IBaseResource resolvedResource = theTransactionDetails.getResolvedResource(reference); 352 353 // And the usual case is that the reference points to a resource 354 // elsewhere in the repository, so we load it 355 if (resolvedResource == null 356 && myResourceLinkResolver != null 357 && !reference.getValue().startsWith("urn:uuid:")) { 358 RequestPartitionId targetRequestPartitionId = determineResolverPartitionId(theRequestPartitionId); 359 resolvedResource = myResourceLinkResolver.loadTargetResource( 360 targetRequestPartitionId, 361 theEntity.getResourceType(), 362 thePathAndRef, 363 theRequestDetails, 364 theTransactionDetails); 365 if (resolvedResource != null) { 366 ourLog.trace("Found target: {}", resolvedResource.getIdElement()); 367 theTransactionDetails.addResolvedResource( 368 thePathAndRef.getRef().getReferenceElement(), resolvedResource); 369 } 370 } 371 372 return resolvedResource; 373 } 374 }; 375 extractSearchIndexParametersForTargetResources( 376 theRequestDetails, theParams, theEntity, new HashSet<>(), strategy, theIndexedReferences, false, false); 377 } 378 379 /** 380 * Extract indexes for contained references as well as for uplifted refchains. 381 * These two types of indexes are both similar special cases. Normally we handle 382 * chained searches ("Patient?organization.name=Foo") using a join from the 383 * {@link ResourceLink} table (for the "organization" part) to the 384 * {@link ResourceIndexedSearchParamString} table (for the "name" part). But 385 * for both contained resource indexes and uplifted refchains we use only the 386 * {@link ResourceIndexedSearchParamString} table to handle the entire 387 * "organization.name" part, or the other similar tables for token, number, etc. 388 * 389 * @see #extractSearchIndexParametersForContainedResources(RequestDetails, ResourceIndexedSearchParams, IBaseResource, ResourceTable, ISearchParamExtractor.SearchParamSet) 390 * @see #extractSearchIndexParametersForUpliftedRefchains(RequestDetails, ResourceIndexedSearchParams, ResourceTable, RequestPartitionId, TransactionDetails, ISearchParamExtractor.SearchParamSet) 391 */ 392 private void extractSearchIndexParametersForTargetResources( 393 RequestDetails theRequestDetails, 394 ResourceIndexedSearchParams theParams, 395 ResourceTable theEntity, 396 Collection<IBaseResource> theAlreadySeenResources, 397 IChainedSearchParameterExtractionStrategy theTargetIndexingStrategy, 398 ISearchParamExtractor.SearchParamSet<PathAndRef> theIndexedReferences, 399 boolean theRecurse, 400 boolean theIndexOnContainedResources) { 401 // 2. Find referenced search parameters 402 403 String spnamePrefix; 404 // 3. for each referenced search parameter, create an index 405 for (PathAndRef nextPathAndRef : theIndexedReferences) { 406 407 // 3.1 get the search parameter name as spname prefix 408 spnamePrefix = nextPathAndRef.getSearchParamName(); 409 410 if (spnamePrefix == null || (nextPathAndRef.getRef() == null && nextPathAndRef.getResource() == null)) 411 continue; 412 413 // 3.1.2 check if this ref actually applies here 414 ISearchParamExtractor.ISearchParamFilter searchParamsToIndex = 415 theTargetIndexingStrategy.getSearchParamFilter(nextPathAndRef); 416 if (searchParamsToIndex == ISearchParamExtractor.NO_PARAMS) { 417 continue; 418 } 419 420 // 3.2 find the target resource 421 IBaseResource targetResource = theTargetIndexingStrategy.fetchResourceAtPath(nextPathAndRef); 422 if (targetResource == null) continue; 423 424 // 3.2.1 if we've already processed this resource upstream, do not process it again, to prevent infinite 425 // loops 426 if (theAlreadySeenResources.contains(targetResource)) { 427 continue; 428 } 429 430 ResourceIndexedSearchParams currParams = new ResourceIndexedSearchParams(); 431 432 // 3.3 create indexes for the current contained resource 433 extractSearchIndexParameters(theRequestDetails, currParams, targetResource, searchParamsToIndex); 434 435 // 3.4 recurse to process any other contained resources referenced by this one 436 // Recursing is currently only allowed for contained resources and not 437 // uplifted refchains because the latter could potentially kill performance 438 // with the number of resource resolutions needed in order to handle 439 // a single write. Maybe in the future we could add caching to improve 440 // this 441 if (theRecurse) { 442 HashSet<IBaseResource> nextAlreadySeenResources = new HashSet<>(theAlreadySeenResources); 443 nextAlreadySeenResources.add(targetResource); 444 445 ISearchParamExtractor.SearchParamSet<PathAndRef> indexedReferences = 446 mySearchParamExtractor.extractResourceLinks(targetResource, theIndexOnContainedResources); 447 SearchParamExtractorService.handleWarnings( 448 theRequestDetails, myInterceptorBroadcaster, indexedReferences); 449 450 extractSearchIndexParametersForTargetResources( 451 theRequestDetails, 452 currParams, 453 theEntity, 454 nextAlreadySeenResources, 455 theTargetIndexingStrategy, 456 indexedReferences, 457 true, 458 theIndexOnContainedResources); 459 } 460 461 // 3.5 added reference name as a prefix for the contained resource if any 462 // e.g. for Observation.subject contained reference 463 // the SP_NAME = subject.family 464 currParams.updateSpnamePrefixForIndexOnUpliftedChain( 465 theEntity.getResourceType(), nextPathAndRef.getSearchParamName()); 466 467 // 3.6 merge to the mainParams 468 // NOTE: the spname prefix is different 469 mergeParams(currParams, theParams); 470 } 471 } 472 473 private IBaseResource findContainedResource(Collection<IBaseResource> resources, IBaseReference reference) { 474 for (IBaseResource resource : resources) { 475 if (resource.getIdElement().equals(reference.getReferenceElement())) return resource; 476 } 477 return null; 478 } 479 480 private void mergeParams(ResourceIndexedSearchParams theSrcParams, ResourceIndexedSearchParams theTargetParams) { 481 482 theTargetParams.myNumberParams.addAll(theSrcParams.myNumberParams); 483 theTargetParams.myQuantityParams.addAll(theSrcParams.myQuantityParams); 484 theTargetParams.myQuantityNormalizedParams.addAll(theSrcParams.myQuantityNormalizedParams); 485 theTargetParams.myDateParams.addAll(theSrcParams.myDateParams); 486 theTargetParams.myUriParams.addAll(theSrcParams.myUriParams); 487 theTargetParams.myTokenParams.addAll(theSrcParams.myTokenParams); 488 theTargetParams.myStringParams.addAll(theSrcParams.myStringParams); 489 theTargetParams.myCoordsParams.addAll(theSrcParams.myCoordsParams); 490 theTargetParams.myCompositeParams.addAll(theSrcParams.myCompositeParams); 491 } 492 493 void extractSearchIndexParameters( 494 RequestDetails theRequestDetails, 495 ResourceIndexedSearchParams theParams, 496 IBaseResource theResource, 497 @Nonnull ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { 498 499 // Strings 500 ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamString> strings = 501 extractSearchParamStrings(theResource, theSearchParamFilter); 502 handleWarnings(theRequestDetails, myInterceptorBroadcaster, strings); 503 theParams.myStringParams.addAll(strings); 504 505 // Numbers 506 ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamNumber> numbers = 507 extractSearchParamNumber(theResource, theSearchParamFilter); 508 handleWarnings(theRequestDetails, myInterceptorBroadcaster, numbers); 509 theParams.myNumberParams.addAll(numbers); 510 511 // Quantities 512 ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamQuantity> quantities = 513 extractSearchParamQuantity(theResource, theSearchParamFilter); 514 handleWarnings(theRequestDetails, myInterceptorBroadcaster, quantities); 515 theParams.myQuantityParams.addAll(quantities); 516 517 if (myStorageSettings 518 .getNormalizedQuantitySearchLevel() 519 .equals(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_STORAGE_SUPPORTED) 520 || myStorageSettings 521 .getNormalizedQuantitySearchLevel() 522 .equals(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED)) { 523 ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamQuantityNormalized> quantitiesNormalized = 524 extractSearchParamQuantityNormalized(theResource, theSearchParamFilter); 525 handleWarnings(theRequestDetails, myInterceptorBroadcaster, quantitiesNormalized); 526 theParams.myQuantityNormalizedParams.addAll(quantitiesNormalized); 527 } 528 529 // Dates 530 ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamDate> dates = 531 extractSearchParamDates(theResource, theSearchParamFilter); 532 handleWarnings(theRequestDetails, myInterceptorBroadcaster, dates); 533 theParams.myDateParams.addAll(dates); 534 535 // URIs 536 ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamUri> uris = 537 extractSearchParamUri(theResource, theSearchParamFilter); 538 handleWarnings(theRequestDetails, myInterceptorBroadcaster, uris); 539 theParams.myUriParams.addAll(uris); 540 541 // Tokens (can result in both Token and String, as we index the display name for 542 // the types: Coding, CodeableConcept) 543 ISearchParamExtractor.SearchParamSet<BaseResourceIndexedSearchParam> tokens = 544 extractSearchParamTokens(theResource, theSearchParamFilter); 545 for (BaseResourceIndexedSearchParam next : tokens) { 546 if (next instanceof ResourceIndexedSearchParamToken) { 547 theParams.myTokenParams.add((ResourceIndexedSearchParamToken) next); 548 } else if (next instanceof ResourceIndexedSearchParamCoords) { 549 theParams.myCoordsParams.add((ResourceIndexedSearchParamCoords) next); 550 } else { 551 theParams.myStringParams.add((ResourceIndexedSearchParamString) next); 552 } 553 } 554 555 // Composites 556 // dst2 composites use stuff like value[x] , and we don't support them. 557 if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { 558 ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamComposite> composites = 559 extractSearchParamComposites(theResource, theSearchParamFilter); 560 handleWarnings(theRequestDetails, myInterceptorBroadcaster, composites); 561 theParams.myCompositeParams.addAll(composites); 562 } 563 564 // Specials 565 ISearchParamExtractor.SearchParamSet<BaseResourceIndexedSearchParam> specials = 566 extractSearchParamSpecial(theResource, theSearchParamFilter); 567 for (BaseResourceIndexedSearchParam next : specials) { 568 if (next instanceof ResourceIndexedSearchParamCoords) { 569 theParams.myCoordsParams.add((ResourceIndexedSearchParamCoords) next); 570 } 571 } 572 } 573 574 private void populateResourceTables(ResourceIndexedSearchParams theParams, ResourceTable theEntity) { 575 576 populateResourceTable(theParams.myNumberParams, theEntity); 577 populateResourceTable(theParams.myQuantityParams, theEntity); 578 populateResourceTable(theParams.myQuantityNormalizedParams, theEntity); 579 populateResourceTable(theParams.myDateParams, theEntity); 580 populateResourceTable(theParams.myUriParams, theEntity); 581 populateResourceTable(theParams.myTokenParams, theEntity); 582 populateResourceTable(theParams.myStringParams, theEntity); 583 populateResourceTable(theParams.myCoordsParams, theEntity); 584 } 585 586 @VisibleForTesting 587 public void setContext(FhirContext theContext) { 588 myContext = theContext; 589 } 590 591 private void extractResourceLinks( 592 RequestPartitionId theRequestPartitionId, 593 ResourceIndexedSearchParams theParams, 594 ResourceTable theEntity, 595 IBaseResource theResource, 596 TransactionDetails theTransactionDetails, 597 boolean theFailOnInvalidReference, 598 RequestDetails theRequest, 599 ISearchParamExtractor.SearchParamSet<PathAndRef> theIndexedReferences) { 600 extractResourceLinks( 601 theRequestPartitionId, 602 new ResourceIndexedSearchParams(), 603 theParams, 604 theEntity, 605 theResource, 606 theTransactionDetails, 607 theFailOnInvalidReference, 608 theRequest, 609 theIndexedReferences); 610 } 611 612 private void extractResourceLinks( 613 RequestPartitionId theRequestPartitionId, 614 ResourceIndexedSearchParams theExistingParams, 615 ResourceIndexedSearchParams theNewParams, 616 ResourceTable theEntity, 617 IBaseResource theResource, 618 TransactionDetails theTransactionDetails, 619 boolean theFailOnInvalidReference, 620 RequestDetails theRequest, 621 ISearchParamExtractor.SearchParamSet<PathAndRef> theIndexedReferences) { 622 String sourceResourceName = myContext.getResourceType(theResource); 623 624 for (PathAndRef nextPathAndRef : theIndexedReferences) { 625 if (nextPathAndRef.getRef() != null) { 626 if (nextPathAndRef.getRef().getReferenceElement().isLocal()) { 627 continue; 628 } 629 630 RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam( 631 sourceResourceName, nextPathAndRef.getSearchParamName()); 632 extractResourceLinks( 633 theRequestPartitionId, 634 theExistingParams, 635 theNewParams, 636 theEntity, 637 theTransactionDetails, 638 sourceResourceName, 639 searchParam, 640 nextPathAndRef, 641 theFailOnInvalidReference, 642 theRequest); 643 } 644 } 645 646 theEntity.setHasLinks(theNewParams.myLinks.size() > 0); 647 } 648 649 private void extractResourceLinks( 650 @Nonnull RequestPartitionId theRequestPartitionId, 651 ResourceIndexedSearchParams theExistingParams, 652 ResourceIndexedSearchParams theNewParams, 653 ResourceTable theEntity, 654 TransactionDetails theTransactionDetails, 655 String theSourceResourceName, 656 RuntimeSearchParam theRuntimeSearchParam, 657 PathAndRef thePathAndRef, 658 boolean theFailOnInvalidReference, 659 RequestDetails theRequest) { 660 IBaseReference nextReference = thePathAndRef.getRef(); 661 IIdType nextId = nextReference.getReferenceElement(); 662 String path = thePathAndRef.getPath(); 663 Date transactionDate = theTransactionDetails.getTransactionDate(); 664 665 /* 666 * This can only really happen if the DAO is being called 667 * programmatically with a Bundle (not through the FHIR REST API) 668 * but Smile does this 669 */ 670 if (nextId.isEmpty() && nextReference.getResource() != null) { 671 nextId = nextReference.getResource().getIdElement(); 672 } 673 674 if (myContext.getParserOptions().isStripVersionsFromReferences() 675 && !myContext 676 .getParserOptions() 677 .getDontStripVersionsFromReferencesAtPaths() 678 .contains(thePathAndRef.getPath()) 679 && nextId.hasVersionIdPart()) { 680 nextId = nextId.toVersionless(); 681 } 682 683 theNewParams.myPopulatedResourceLinkParameters.add(thePathAndRef.getSearchParamName()); 684 685 boolean canonical = thePathAndRef.isCanonical(); 686 if (LogicalReferenceHelper.isLogicalReference(myStorageSettings, nextId) || canonical) { 687 String value = nextId.getValue(); 688 ResourceLink resourceLink = 689 ResourceLink.forLogicalReference(thePathAndRef.getPath(), theEntity, value, transactionDate); 690 if (theNewParams.myLinks.add(resourceLink)) { 691 ourLog.debug("Indexing remote resource reference URL: {}", nextId); 692 } 693 return; 694 } 695 696 String baseUrl = nextId.getBaseUrl(); 697 698 // If this is a conditional URL, the part after the question mark 699 // can include URLs (e.g. token system URLs) and these really confuse 700 // the IdType parser because a conditional URL isn't actually a valid 701 // FHIR ID. So in order to truly determine whether we're dealing with 702 // an absolute reference, we strip the query part and reparse 703 // the reference. 704 int questionMarkIndex = nextId.getValue().indexOf('?'); 705 if (questionMarkIndex != -1) { 706 IdType preQueryId = new IdType(nextId.getValue().substring(0, questionMarkIndex - 1)); 707 baseUrl = preQueryId.getBaseUrl(); 708 } 709 710 String typeString = nextId.getResourceType(); 711 if (isBlank(typeString)) { 712 String msg = "Invalid resource reference found at path[" + path + "] - Does not contain resource type - " 713 + nextId.getValue(); 714 if (theFailOnInvalidReference) { 715 throw new InvalidRequestException(Msg.code(505) + msg); 716 } else { 717 ourLog.debug(msg); 718 return; 719 } 720 } 721 RuntimeResourceDefinition resourceDefinition; 722 try { 723 resourceDefinition = myContext.getResourceDefinition(typeString); 724 } catch (DataFormatException e) { 725 String msg = "Invalid resource reference found at path[" + path 726 + "] - Resource type is unknown or not supported on this server - " + nextId.getValue(); 727 if (theFailOnInvalidReference) { 728 throw new InvalidRequestException(Msg.code(506) + msg); 729 } else { 730 ourLog.debug(msg); 731 return; 732 } 733 } 734 735 if (theRuntimeSearchParam.hasTargets()) { 736 if (!theRuntimeSearchParam.getTargets().contains(typeString)) { 737 return; 738 } 739 } 740 741 if (isNotBlank(baseUrl)) { 742 if (!myStorageSettings.getTreatBaseUrlsAsLocal().contains(baseUrl) 743 && !myStorageSettings.isAllowExternalReferences()) { 744 String msg = myContext 745 .getLocalizer() 746 .getMessage(BaseSearchParamExtractor.class, "externalReferenceNotAllowed", nextId.getValue()); 747 throw new InvalidRequestException(Msg.code(507) + msg); 748 } else { 749 ResourceLink resourceLink = 750 ResourceLink.forAbsoluteReference(thePathAndRef.getPath(), theEntity, nextId, transactionDate); 751 if (theNewParams.myLinks.add(resourceLink)) { 752 ourLog.debug("Indexing remote resource reference URL: {}", nextId); 753 } 754 return; 755 } 756 } 757 758 Class<? extends IBaseResource> type = resourceDefinition.getImplementingClass(); 759 String targetId = nextId.getIdPart(); 760 if (StringUtils.isBlank(targetId)) { 761 String msg = "Invalid resource reference found at path[" + path + "] - Does not contain resource ID - " 762 + nextId.getValue(); 763 if (theFailOnInvalidReference) { 764 throw new InvalidRequestException(Msg.code(508) + msg); 765 } else { 766 ourLog.debug(msg); 767 return; 768 } 769 } 770 771 IIdType referenceElement = thePathAndRef.getRef().getReferenceElement(); 772 JpaPid resolvedTargetId = (JpaPid) theTransactionDetails.getResolvedResourceId(referenceElement); 773 ResourceLink resourceLink; 774 775 Long targetVersionId = nextId.getVersionIdPartAsLong(); 776 if (resolvedTargetId != null) { 777 778 /* 779 * If we have already resolved the given reference within this transaction, we don't 780 * need to resolve it again 781 */ 782 myResourceLinkResolver.validateTypeOrThrowException(type); 783 resourceLink = ResourceLink.forLocalReference( 784 thePathAndRef.getPath(), 785 theEntity, 786 typeString, 787 resolvedTargetId.getId(), 788 targetId, 789 transactionDate, 790 targetVersionId); 791 792 } else if (theFailOnInvalidReference) { 793 794 /* 795 * The reference points to another resource, so let's look it up. We need to do this 796 * since the target may be a forced ID, but also so that we can throw an exception 797 * if the reference is invalid 798 */ 799 myResourceLinkResolver.validateTypeOrThrowException(type); 800 801 /* 802 * We need to obtain a resourceLink out of the provided {@literal thePathAndRef}. In the case 803 * where we are updating a resource that already has resourceLinks (stored in {@literal theExistingParams.getResourceLinks()}), 804 * let's try to match thePathAndRef to an already existing resourceLink to avoid the 805 * very expensive operation of creating a resourceLink that would end up being exactly the same 806 * one we already have. 807 */ 808 Optional<ResourceLink> optionalResourceLink = 809 findMatchingResourceLink(thePathAndRef, theExistingParams.getResourceLinks()); 810 if (optionalResourceLink.isPresent()) { 811 resourceLink = optionalResourceLink.get(); 812 } else { 813 resourceLink = resolveTargetAndCreateResourceLinkOrReturnNull( 814 theRequestPartitionId, 815 theSourceResourceName, 816 thePathAndRef, 817 theEntity, 818 transactionDate, 819 nextId, 820 theRequest, 821 theTransactionDetails); 822 } 823 824 if (resourceLink == null) { 825 return; 826 } else { 827 // Cache the outcome in the current transaction in case there are more references 828 JpaPid persistentId = JpaPid.fromId(resourceLink.getTargetResourcePid()); 829 theTransactionDetails.addResolvedResourceId(referenceElement, persistentId); 830 } 831 832 } else { 833 834 /* 835 * Just assume the reference is valid. This is used for in-memory matching since there 836 * is no expectation of a database in this situation 837 */ 838 ResourceTable target; 839 target = new ResourceTable(); 840 target.setResourceType(typeString); 841 resourceLink = ResourceLink.forLocalReference( 842 thePathAndRef.getPath(), theEntity, typeString, null, targetId, transactionDate, targetVersionId); 843 } 844 845 theNewParams.myLinks.add(resourceLink); 846 } 847 848 private Optional<ResourceLink> findMatchingResourceLink( 849 PathAndRef thePathAndRef, Collection<ResourceLink> theResourceLinks) { 850 IIdType referenceElement = thePathAndRef.getRef().getReferenceElement(); 851 List<ResourceLink> resourceLinks = new ArrayList<>(theResourceLinks); 852 for (ResourceLink resourceLink : resourceLinks) { 853 854 // comparing the searchParam path ex: Group.member.entity 855 boolean hasMatchingSearchParamPath = 856 StringUtils.equals(resourceLink.getSourcePath(), thePathAndRef.getPath()); 857 858 boolean hasMatchingResourceType = 859 StringUtils.equals(resourceLink.getTargetResourceType(), referenceElement.getResourceType()); 860 861 boolean hasMatchingResourceId = 862 StringUtils.equals(resourceLink.getTargetResourceId(), referenceElement.getIdPart()); 863 864 boolean hasMatchingResourceVersion = myContext.getParserOptions().isStripVersionsFromReferences() 865 || referenceElement.getVersionIdPartAsLong() == null 866 || referenceElement.getVersionIdPartAsLong().equals(resourceLink.getTargetResourceVersion()); 867 868 if (hasMatchingSearchParamPath 869 && hasMatchingResourceType 870 && hasMatchingResourceId 871 && hasMatchingResourceVersion) { 872 return Optional.of(resourceLink); 873 } 874 } 875 876 return Optional.empty(); 877 } 878 879 private void extractResourceLinksForContainedResources( 880 RequestPartitionId theRequestPartitionId, 881 ResourceIndexedSearchParams theParams, 882 ResourceTable theEntity, 883 IBaseResource theResource, 884 TransactionDetails theTransactionDetails, 885 boolean theFailOnInvalidReference, 886 RequestDetails theRequest) { 887 888 FhirTerser terser = myContext.newTerser(); 889 890 // 1. get all contained resources 891 Collection<IBaseResource> containedResources = terser.getAllEmbeddedResources(theResource, false); 892 893 extractResourceLinksForContainedResources( 894 theRequestPartitionId, 895 theParams, 896 theEntity, 897 theResource, 898 theTransactionDetails, 899 theFailOnInvalidReference, 900 theRequest, 901 containedResources, 902 new HashSet<>()); 903 } 904 905 private void extractResourceLinksForContainedResources( 906 RequestPartitionId theRequestPartitionId, 907 ResourceIndexedSearchParams theParams, 908 ResourceTable theEntity, 909 IBaseResource theResource, 910 TransactionDetails theTransactionDetails, 911 boolean theFailOnInvalidReference, 912 RequestDetails theRequest, 913 Collection<IBaseResource> theContainedResources, 914 Collection<IBaseResource> theAlreadySeenResources) { 915 916 // 2. Find referenced search parameters 917 ISearchParamExtractor.SearchParamSet<PathAndRef> referencedSearchParamSet = 918 mySearchParamExtractor.extractResourceLinks(theResource, true); 919 920 String spNamePrefix; 921 ResourceIndexedSearchParams currParams; 922 // 3. for each referenced search parameter, create an index 923 for (PathAndRef nextPathAndRef : referencedSearchParamSet) { 924 925 // 3.1 get the search parameter name as spname prefix 926 spNamePrefix = nextPathAndRef.getSearchParamName(); 927 928 if (spNamePrefix == null || nextPathAndRef.getRef() == null) continue; 929 930 // 3.2 find the contained resource 931 IBaseResource containedResource = findContainedResource(theContainedResources, nextPathAndRef.getRef()); 932 if (containedResource == null) continue; 933 934 // 3.2.1 if we've already processed this resource upstream, do not process it again, to prevent infinite 935 // loops 936 if (theAlreadySeenResources.contains(containedResource)) { 937 continue; 938 } 939 940 currParams = new ResourceIndexedSearchParams(); 941 942 // 3.3 create indexes for the current contained resource 943 ISearchParamExtractor.SearchParamSet<PathAndRef> indexedReferences = 944 mySearchParamExtractor.extractResourceLinks(containedResource, true); 945 extractResourceLinks( 946 theRequestPartitionId, 947 currParams, 948 theEntity, 949 containedResource, 950 theTransactionDetails, 951 theFailOnInvalidReference, 952 theRequest, 953 indexedReferences); 954 955 // 3.4 recurse to process any other contained resources referenced by this one 956 if (myStorageSettings.isIndexOnContainedResourcesRecursively()) { 957 HashSet<IBaseResource> nextAlreadySeenResources = new HashSet<>(theAlreadySeenResources); 958 nextAlreadySeenResources.add(containedResource); 959 extractResourceLinksForContainedResources( 960 theRequestPartitionId, 961 currParams, 962 theEntity, 963 containedResource, 964 theTransactionDetails, 965 theFailOnInvalidReference, 966 theRequest, 967 theContainedResources, 968 nextAlreadySeenResources); 969 } 970 971 // 3.4 added reference name as a prefix for the contained resource if any 972 // e.g. for Observation.subject contained reference 973 // the SP_NAME = subject.family 974 currParams.updateSpnamePrefixForLinksOnContainedResource(nextPathAndRef.getPath()); 975 976 // 3.5 merge to the mainParams 977 // NOTE: the spname prefix is different 978 theParams.getResourceLinks().addAll(currParams.getResourceLinks()); 979 } 980 } 981 982 @SuppressWarnings("unchecked") 983 private ResourceLink resolveTargetAndCreateResourceLinkOrReturnNull( 984 @Nonnull RequestPartitionId theRequestPartitionId, 985 String theSourceResourceName, 986 PathAndRef thePathAndRef, 987 ResourceTable theEntity, 988 Date theUpdateTime, 989 IIdType theNextId, 990 RequestDetails theRequest, 991 TransactionDetails theTransactionDetails) { 992 JpaPid resolvedResourceId = (JpaPid) theTransactionDetails.getResolvedResourceId(theNextId); 993 if (resolvedResourceId != null) { 994 String targetResourceType = theNextId.getResourceType(); 995 Long targetResourcePid = resolvedResourceId.getId(); 996 String targetResourceIdPart = theNextId.getIdPart(); 997 Long targetVersion = theNextId.getVersionIdPartAsLong(); 998 return ResourceLink.forLocalReference( 999 thePathAndRef.getPath(), 1000 theEntity, 1001 targetResourceType, 1002 targetResourcePid, 1003 targetResourceIdPart, 1004 theUpdateTime, 1005 targetVersion); 1006 } 1007 1008 /* 1009 * We keep a cache of resolved target resources. This is good since for some resource types, there 1010 * are multiple search parameters that map to the same element path within a resource (e.g. 1011 * Observation:patient and Observation.subject and we don't want to force a resolution of the 1012 * target any more times than we have to. 1013 */ 1014 1015 IResourceLookup<JpaPid> targetResource; 1016 if (myPartitionSettings.isPartitioningEnabled()) { 1017 if (myPartitionSettings.getAllowReferencesAcrossPartitions() 1018 == PartitionSettings.CrossPartitionReferenceMode.ALLOWED_UNQUALIFIED) { 1019 1020 // Interceptor: Pointcut.JPA_CROSS_PARTITION_REFERENCE_DETECTED 1021 if (CompositeInterceptorBroadcaster.hasHooks( 1022 Pointcut.JPA_RESOLVE_CROSS_PARTITION_REFERENCE, myInterceptorBroadcaster, theRequest)) { 1023 CrossPartitionReferenceDetails referenceDetails = new CrossPartitionReferenceDetails( 1024 theRequestPartitionId, 1025 theSourceResourceName, 1026 thePathAndRef, 1027 theRequest, 1028 theTransactionDetails); 1029 HookParams params = new HookParams(referenceDetails); 1030 targetResource = 1031 (IResourceLookup<JpaPid>) CompositeInterceptorBroadcaster.doCallHooksAndReturnObject( 1032 myInterceptorBroadcaster, 1033 theRequest, 1034 Pointcut.JPA_RESOLVE_CROSS_PARTITION_REFERENCE, 1035 params); 1036 } else { 1037 targetResource = myResourceLinkResolver.findTargetResource( 1038 RequestPartitionId.allPartitions(), 1039 theSourceResourceName, 1040 thePathAndRef, 1041 theRequest, 1042 theTransactionDetails); 1043 } 1044 1045 } else { 1046 targetResource = myResourceLinkResolver.findTargetResource( 1047 theRequestPartitionId, theSourceResourceName, thePathAndRef, theRequest, theTransactionDetails); 1048 } 1049 } else { 1050 targetResource = myResourceLinkResolver.findTargetResource( 1051 theRequestPartitionId, theSourceResourceName, thePathAndRef, theRequest, theTransactionDetails); 1052 } 1053 1054 if (targetResource == null) { 1055 return null; 1056 } 1057 1058 String targetResourceType = targetResource.getResourceType(); 1059 Long targetResourcePid = targetResource.getPersistentId().getId(); 1060 String targetResourceIdPart = theNextId.getIdPart(); 1061 Long targetVersion = theNextId.getVersionIdPartAsLong(); 1062 return ResourceLink.forLocalReference( 1063 thePathAndRef.getPath(), 1064 theEntity, 1065 targetResourceType, 1066 targetResourcePid, 1067 targetResourceIdPart, 1068 theUpdateTime, 1069 targetVersion); 1070 } 1071 1072 private RequestPartitionId determineResolverPartitionId(@Nonnull RequestPartitionId theRequestPartitionId) { 1073 RequestPartitionId targetRequestPartitionId = theRequestPartitionId; 1074 if (myPartitionSettings.isPartitioningEnabled() 1075 && myPartitionSettings.getAllowReferencesAcrossPartitions() 1076 == PartitionSettings.CrossPartitionReferenceMode.ALLOWED_UNQUALIFIED) { 1077 targetRequestPartitionId = RequestPartitionId.allPartitions(); 1078 } 1079 return targetRequestPartitionId; 1080 } 1081 1082 private void populateResourceTable( 1083 Collection<? extends BaseResourceIndexedSearchParam> theParams, ResourceTable theResourceTable) { 1084 for (BaseResourceIndexedSearchParam next : theParams) { 1085 if (next.getResourcePid() == null) { 1086 next.setResource(theResourceTable); 1087 } 1088 } 1089 } 1090 1091 private void populateResourceTableForComboParams( 1092 Collection<? extends IResourceIndexComboSearchParameter> theParams, ResourceTable theResourceTable) { 1093 for (IResourceIndexComboSearchParameter next : theParams) { 1094 if (next.getResource() == null) { 1095 next.setResource(theResourceTable); 1096 if (next instanceof BasePartitionable) { 1097 ((BasePartitionable) next).setPartitionId(theResourceTable.getPartitionId()); 1098 } 1099 } 1100 } 1101 } 1102 1103 private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamDate> extractSearchParamDates( 1104 IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { 1105 return mySearchParamExtractor.extractSearchParamDates(theResource, theSearchParamFilter); 1106 } 1107 1108 private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber( 1109 IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { 1110 return mySearchParamExtractor.extractSearchParamNumber(theResource, theSearchParamFilter); 1111 } 1112 1113 private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity( 1114 IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { 1115 return mySearchParamExtractor.extractSearchParamQuantity(theResource, theSearchParamFilter); 1116 } 1117 1118 private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamQuantityNormalized> 1119 extractSearchParamQuantityNormalized( 1120 IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { 1121 return mySearchParamExtractor.extractSearchParamQuantityNormalized(theResource, theSearchParamFilter); 1122 } 1123 1124 private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamString> extractSearchParamStrings( 1125 IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { 1126 return mySearchParamExtractor.extractSearchParamStrings(theResource, theSearchParamFilter); 1127 } 1128 1129 private ISearchParamExtractor.SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens( 1130 IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { 1131 return mySearchParamExtractor.extractSearchParamTokens(theResource, theSearchParamFilter); 1132 } 1133 1134 private ISearchParamExtractor.SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamSpecial( 1135 IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { 1136 return mySearchParamExtractor.extractSearchParamSpecial(theResource, theSearchParamFilter); 1137 } 1138 1139 private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamUri> extractSearchParamUri( 1140 IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { 1141 return mySearchParamExtractor.extractSearchParamUri(theResource, theSearchParamFilter); 1142 } 1143 1144 private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamComposite> extractSearchParamComposites( 1145 IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) { 1146 return mySearchParamExtractor.extractSearchParamComposites(theResource, theSearchParamFilter); 1147 } 1148 1149 @VisibleForTesting 1150 void setInterceptorBroadcasterForUnitTest(IInterceptorBroadcaster theInterceptorBroadcaster) { 1151 myInterceptorBroadcaster = theInterceptorBroadcaster; 1152 } 1153 1154 @Nonnull 1155 public List<String> extractParamValuesAsStrings( 1156 RuntimeSearchParam theActiveSearchParam, IBaseResource theResource) { 1157 return mySearchParamExtractor.extractParamValuesAsStrings(theActiveSearchParam, theResource); 1158 } 1159 1160 public void extractSearchParamComboUnique(ResourceTable theEntity, ResourceIndexedSearchParams theParams) { 1161 String resourceType = theEntity.getResourceType(); 1162 Set<ResourceIndexedComboStringUnique> comboUniques = 1163 mySearchParamExtractor.extractSearchParamComboUnique(resourceType, theParams); 1164 theParams.myComboStringUniques.addAll(comboUniques); 1165 populateResourceTableForComboParams(theParams.myComboStringUniques, theEntity); 1166 } 1167 1168 public void extractSearchParamComboNonUnique(ResourceTable theEntity, ResourceIndexedSearchParams theParams) { 1169 String resourceType = theEntity.getResourceType(); 1170 Set<ResourceIndexedComboTokenNonUnique> comboNonUniques = 1171 mySearchParamExtractor.extractSearchParamComboNonUnique(resourceType, theParams); 1172 theParams.myComboTokenNonUnique.addAll(comboNonUniques); 1173 populateResourceTableForComboParams(theParams.myComboTokenNonUnique, theEntity); 1174 } 1175 1176 /** 1177 * This interface is used by {@link #extractSearchIndexParametersForTargetResources(RequestDetails, ResourceIndexedSearchParams, ResourceTable, Collection, IChainedSearchParameterExtractionStrategy, ISearchParamExtractor.SearchParamSet, boolean, boolean)} 1178 * in order to use that method for extracting chained search parameter indexes both 1179 * from contained resources and from uplifted refchains. 1180 */ 1181 private interface IChainedSearchParameterExtractionStrategy { 1182 1183 /** 1184 * Which search parameters should be indexed for the resource target 1185 * at the given path. In other words if thePathAndRef contains 1186 * "Patient/123", then we could return a filter that only lets the 1187 * "name" and "gender" search params through if we only want those 1188 * two parameters to be indexed for the resolved Patient resource 1189 * with that ID. 1190 */ 1191 @Nonnull 1192 ISearchParamExtractor.ISearchParamFilter getSearchParamFilter(@Nonnull PathAndRef thePathAndRef); 1193 1194 /** 1195 * Actually fetch the resource at the given path, or return 1196 * {@literal null} if none can be found. 1197 */ 1198 @Nullable 1199 IBaseResource fetchResourceAtPath(@Nonnull PathAndRef thePathAndRef); 1200 } 1201 1202 static void handleWarnings( 1203 RequestDetails theRequestDetails, 1204 IInterceptorBroadcaster theInterceptorBroadcaster, 1205 ISearchParamExtractor.SearchParamSet<?> theSearchParamSet) { 1206 if (theSearchParamSet.getWarnings().isEmpty()) { 1207 return; 1208 } 1209 1210 // If extraction generated any warnings, broadcast an error 1211 if (CompositeInterceptorBroadcaster.hasHooks( 1212 Pointcut.JPA_PERFTRACE_WARNING, theInterceptorBroadcaster, theRequestDetails)) { 1213 for (String next : theSearchParamSet.getWarnings()) { 1214 StorageProcessingMessage messageHolder = new StorageProcessingMessage(); 1215 messageHolder.setMessage(next); 1216 HookParams params = new HookParams() 1217 .add(RequestDetails.class, theRequestDetails) 1218 .addIfMatchesType(ServletRequestDetails.class, theRequestDetails) 1219 .add(StorageProcessingMessage.class, messageHolder); 1220 CompositeInterceptorBroadcaster.doCallHooks( 1221 theInterceptorBroadcaster, theRequestDetails, Pointcut.JPA_PERFTRACE_WARNING, params); 1222 } 1223 } 1224 } 1225}