001/* 002 * #%L 003 * HAPI FHIR JPA - Search Parameters 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.jpa.searchparam.extractor; 021 022import ca.uhn.fhir.context.BaseRuntimeChildDefinition; 023import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; 024import ca.uhn.fhir.context.BaseRuntimeElementDefinition; 025import ca.uhn.fhir.context.ComboSearchParamType; 026import ca.uhn.fhir.context.FhirContext; 027import ca.uhn.fhir.context.FhirVersionEnum; 028import ca.uhn.fhir.context.RuntimeResourceDefinition; 029import ca.uhn.fhir.context.RuntimeSearchParam; 030import ca.uhn.fhir.i18n.Msg; 031import ca.uhn.fhir.jpa.model.config.PartitionSettings; 032import ca.uhn.fhir.jpa.model.entity.StorageSettings; 033import ca.uhn.fhir.jpa.model.entity.*; 034import ca.uhn.fhir.jpa.model.util.UcumServiceUtil; 035import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; 036import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil; 037import ca.uhn.fhir.jpa.searchparam.util.RuntimeSearchParamHelper; 038import ca.uhn.fhir.model.api.IQueryParameterType; 039import ca.uhn.fhir.model.primitive.BoundCodeDt; 040import ca.uhn.fhir.rest.api.Constants; 041import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; 042import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 043import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; 044import ca.uhn.fhir.util.FhirTerser; 045import ca.uhn.fhir.util.HapiExtensions; 046import ca.uhn.fhir.util.StringUtil; 047import ca.uhn.fhir.util.UrlUtil; 048import com.google.common.annotations.VisibleForTesting; 049import com.google.common.collect.Sets; 050import jakarta.annotation.Nonnull; 051import jakarta.annotation.Nullable; 052import jakarta.annotation.PostConstruct; 053import org.apache.commons.lang3.ObjectUtils; 054import org.apache.commons.lang3.StringUtils; 055import org.apache.commons.lang3.Validate; 056import org.apache.commons.text.StringTokenizer; 057import org.fhir.ucum.Pair; 058import org.hl7.fhir.exceptions.FHIRException; 059import org.hl7.fhir.instance.model.api.IBase; 060import org.hl7.fhir.instance.model.api.IBaseEnumeration; 061import org.hl7.fhir.instance.model.api.IBaseExtension; 062import org.hl7.fhir.instance.model.api.IBaseReference; 063import org.hl7.fhir.instance.model.api.IBaseResource; 064import org.hl7.fhir.instance.model.api.IIdType; 065import org.hl7.fhir.instance.model.api.IPrimitiveType; 066import org.springframework.beans.factory.annotation.Autowired; 067import org.springframework.context.ApplicationContext; 068 069import java.math.BigDecimal; 070import java.util.ArrayList; 071import java.util.Arrays; 072import java.util.Collection; 073import java.util.Collections; 074import java.util.Date; 075import java.util.HashSet; 076import java.util.List; 077import java.util.Objects; 078import java.util.Optional; 079import java.util.Set; 080import java.util.TreeSet; 081import java.util.stream.Collectors; 082import javax.measure.quantity.Quantity; 083import javax.measure.unit.NonSI; 084import javax.measure.unit.Unit; 085 086import static ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum.DATE; 087import static ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum.REFERENCE; 088import static org.apache.commons.lang3.StringUtils.isBlank; 089import static org.apache.commons.lang3.StringUtils.isNotBlank; 090import static org.apache.commons.lang3.StringUtils.trim; 091 092public abstract class BaseSearchParamExtractor implements ISearchParamExtractor { 093 094 public static final Set<String> COORDS_INDEX_PATHS; 095 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseSearchParamExtractor.class); 096 097 static { 098 Set<String> coordsIndexPaths = Sets.newHashSet("Location.position"); 099 COORDS_INDEX_PATHS = Collections.unmodifiableSet(coordsIndexPaths); 100 } 101 102 @Autowired 103 protected ApplicationContext myApplicationContext; 104 105 @Autowired 106 private FhirContext myContext; 107 108 @Autowired 109 private ISearchParamRegistry mySearchParamRegistry; 110 111 @Autowired 112 private StorageSettings myStorageSettings; 113 114 @Autowired 115 private PartitionSettings myPartitionSettings; 116 117 private Set<String> myIgnoredForSearchDatatypes; 118 private BaseRuntimeChildDefinition myQuantityValueValueChild; 119 private BaseRuntimeChildDefinition myQuantitySystemValueChild; 120 private BaseRuntimeChildDefinition myQuantityCodeValueChild; 121 private BaseRuntimeChildDefinition myMoneyValueChild; 122 private BaseRuntimeChildDefinition myMoneyCurrencyChild; 123 private BaseRuntimeElementCompositeDefinition<?> myLocationPositionDefinition; 124 private BaseRuntimeChildDefinition myCodeSystemUrlValueChild; 125 private BaseRuntimeChildDefinition myRangeLowValueChild; 126 private BaseRuntimeChildDefinition myRangeHighValueChild; 127 private BaseRuntimeChildDefinition myAddressLineValueChild; 128 private BaseRuntimeChildDefinition myAddressCityValueChild; 129 private BaseRuntimeChildDefinition myAddressDistrictValueChild; 130 private BaseRuntimeChildDefinition myAddressStateValueChild; 131 private BaseRuntimeChildDefinition myAddressCountryValueChild; 132 private BaseRuntimeChildDefinition myAddressPostalCodeValueChild; 133 private BaseRuntimeChildDefinition myCapabilityStatementRestSecurityServiceValueChild; 134 private BaseRuntimeChildDefinition myPeriodStartValueChild; 135 private BaseRuntimeChildDefinition myPeriodEndValueChild; 136 private BaseRuntimeChildDefinition myTimingEventValueChild; 137 private BaseRuntimeChildDefinition myTimingRepeatValueChild; 138 private BaseRuntimeChildDefinition myTimingRepeatBoundsValueChild; 139 private BaseRuntimeChildDefinition myDurationSystemValueChild; 140 private BaseRuntimeChildDefinition myDurationCodeValueChild; 141 private BaseRuntimeChildDefinition myDurationValueValueChild; 142 private BaseRuntimeChildDefinition myHumanNameFamilyValueChild; 143 private BaseRuntimeChildDefinition myHumanNameGivenValueChild; 144 private BaseRuntimeChildDefinition myHumanNameTextValueChild; 145 private BaseRuntimeChildDefinition myHumanNamePrefixValueChild; 146 private BaseRuntimeChildDefinition myHumanNameSuffixValueChild; 147 private BaseRuntimeChildDefinition myContactPointValueValueChild; 148 private BaseRuntimeChildDefinition myIdentifierSystemValueChild; 149 private BaseRuntimeChildDefinition myIdentifierValueValueChild; 150 private BaseRuntimeChildDefinition myIdentifierTypeValueChild; 151 private BaseRuntimeChildDefinition myIdentifierTypeTextValueChild; 152 private BaseRuntimeChildDefinition myCodeableConceptCodingValueChild; 153 private BaseRuntimeChildDefinition myCodeableConceptTextValueChild; 154 private BaseRuntimeChildDefinition myCodingSystemValueChild; 155 private BaseRuntimeChildDefinition myCodingCodeValueChild; 156 private BaseRuntimeChildDefinition myCodingDisplayValueChild; 157 private BaseRuntimeChildDefinition myContactPointSystemValueChild; 158 private BaseRuntimeChildDefinition myPatientCommunicationLanguageValueChild; 159 private BaseRuntimeChildDefinition myCodeableReferenceConcept; 160 private BaseRuntimeChildDefinition myCodeableReferenceReference; 161 162 /** 163 * Constructor 164 */ 165 BaseSearchParamExtractor() { 166 super(); 167 } 168 169 /** 170 * UNIT TEST constructor 171 */ 172 BaseSearchParamExtractor( 173 StorageSettings theStorageSettings, 174 PartitionSettings thePartitionSettings, 175 FhirContext theCtx, 176 ISearchParamRegistry theSearchParamRegistry) { 177 Validate.notNull(theStorageSettings); 178 Validate.notNull(theCtx); 179 Validate.notNull(theSearchParamRegistry); 180 181 myStorageSettings = theStorageSettings; 182 myContext = theCtx; 183 mySearchParamRegistry = theSearchParamRegistry; 184 myPartitionSettings = thePartitionSettings; 185 } 186 187 @Override 188 public SearchParamSet<PathAndRef> extractResourceLinks(IBaseResource theResource, boolean theWantLocalReferences) { 189 IExtractor<PathAndRef> extractor = createReferenceExtractor(); 190 return extractSearchParams( 191 theResource, 192 extractor, 193 RestSearchParameterTypeEnum.REFERENCE, 194 theWantLocalReferences, 195 ISearchParamExtractor.ALL_PARAMS); 196 } 197 198 private IExtractor<PathAndRef> createReferenceExtractor() { 199 return new ResourceLinkExtractor(); 200 } 201 202 @Override 203 public PathAndRef extractReferenceLinkFromResource(IBase theValue, String thePath) { 204 ResourceLinkExtractor extractor = new ResourceLinkExtractor(); 205 return extractor.get(theValue, thePath); 206 } 207 208 @Override 209 public List<String> extractParamValuesAsStrings(RuntimeSearchParam theSearchParam, IBaseResource theResource) { 210 IExtractor extractor = createExtractor(theSearchParam, theResource); 211 212 if (theSearchParam.getParamType().equals(REFERENCE)) { 213 return extractReferenceParamsAsQueryTokens(theSearchParam, theResource, extractor); 214 } else { 215 return extractParamsAsQueryTokens(theSearchParam, theResource, extractor); 216 } 217 } 218 219 @Nonnull 220 private IExtractor createExtractor(RuntimeSearchParam theSearchParam, IBaseResource theResource) { 221 IExtractor extractor; 222 switch (theSearchParam.getParamType()) { 223 case DATE: 224 extractor = createDateExtractor(theResource); 225 break; 226 case STRING: 227 extractor = createStringExtractor(theResource); 228 break; 229 case TOKEN: 230 extractor = createTokenExtractor(theResource); 231 break; 232 case NUMBER: 233 extractor = createNumberExtractor(theResource); 234 break; 235 case REFERENCE: 236 extractor = createReferenceExtractor(); 237 break; 238 case QUANTITY: 239 extractor = createQuantityExtractor(theResource); 240 break; 241 case URI: 242 extractor = createUriExtractor(theResource); 243 break; 244 case SPECIAL: 245 extractor = createSpecialExtractor(theResource.getIdElement().getResourceType()); 246 break; 247 case COMPOSITE: 248 case HAS: 249 default: 250 throw new UnsupportedOperationException( 251 Msg.code(503) + "Type " + theSearchParam.getParamType() + " not supported for extraction"); 252 } 253 return extractor; 254 } 255 256 private List<String> extractReferenceParamsAsQueryTokens( 257 RuntimeSearchParam theSearchParam, IBaseResource theResource, IExtractor<PathAndRef> theExtractor) { 258 SearchParamSet<PathAndRef> params = new SearchParamSet<>(); 259 extractSearchParam(theSearchParam, theResource, theExtractor, params, false); 260 return refsToStringList(params); 261 } 262 263 private List<String> refsToStringList(SearchParamSet<PathAndRef> theParams) { 264 return theParams.stream() 265 .map(PathAndRef::getRef) 266 .map(ref -> ref.getReferenceElement().toUnqualifiedVersionless().getValue()) 267 .collect(Collectors.toList()); 268 } 269 270 private <T extends BaseResourceIndexedSearchParam> List<String> extractParamsAsQueryTokens( 271 RuntimeSearchParam theSearchParam, IBaseResource theResource, IExtractor<T> theExtractor) { 272 SearchParamSet<T> params = new SearchParamSet<>(); 273 extractSearchParam(theSearchParam, theResource, theExtractor, params, false); 274 return toStringList(params); 275 } 276 277 private <T extends BaseResourceIndexedSearchParam> List<String> toStringList(SearchParamSet<T> theParams) { 278 return theParams.stream() 279 .map(param -> param.toQueryParameterType().getValueAsQueryToken(myContext)) 280 .collect(Collectors.toList()); 281 } 282 283 @Override 284 public SearchParamSet<ResourceIndexedSearchParamComposite> extractSearchParamComposites( 285 IBaseResource theResource, ISearchParamFilter theSearchParamFilter) { 286 IExtractor<ResourceIndexedSearchParamComposite> extractor = createCompositeExtractor(theResource); 287 return extractSearchParams( 288 theResource, extractor, RestSearchParameterTypeEnum.COMPOSITE, false, theSearchParamFilter); 289 } 290 291 private IExtractor<ResourceIndexedSearchParamComposite> createCompositeExtractor(IBaseResource theResource) { 292 return new CompositeExtractor(theResource); 293 } 294 295 /** 296 * Extractor for composite SPs. 297 * Extracts elements, and then recurses and applies extractors for each component SP using the element as the new root. 298 */ 299 public class CompositeExtractor implements IExtractor<ResourceIndexedSearchParamComposite> { 300 final IBaseResource myResource; 301 final String myResourceType; 302 303 public CompositeExtractor(IBaseResource theResource) { 304 myResource = theResource; 305 myResourceType = toRootTypeName(theResource); 306 } 307 308 /** 309 * Extract the subcomponent index data for each component of a composite SP from an IBase element. 310 * 311 * @param theParams will add 1 or 0 ResourceIndexedSearchParamComposite instances for theValue 312 * @param theCompositeSearchParam the composite SP 313 * @param theValue the focus element for the subcomponent extraction 314 * @param thePath unused api param 315 * @param theWantLocalReferences passed down to reference extraction 316 */ 317 @Override 318 public void extract( 319 SearchParamSet<ResourceIndexedSearchParamComposite> theParams, 320 RuntimeSearchParam theCompositeSearchParam, 321 IBase theValue, 322 String thePath, 323 boolean theWantLocalReferences) { 324 325 // skip broken SPs 326 if (!isExtractableComposite(theCompositeSearchParam)) { 327 ourLog.info( 328 "CompositeExtractor - skipping unsupported search parameter {}", 329 theCompositeSearchParam.getName()); 330 return; 331 } 332 333 String compositeSpName = theCompositeSearchParam.getName(); 334 ourLog.trace("CompositeExtractor - extracting {} {}", compositeSpName, theValue); 335 ResourceIndexedSearchParamComposite e = new ResourceIndexedSearchParamComposite(compositeSpName, thePath); 336 337 // extract the index data for each component. 338 for (RuntimeSearchParam.Component component : theCompositeSearchParam.getComponents()) { 339 String componentSpRef = component.getReference(); 340 String expression = component.getExpression(); 341 342 RuntimeSearchParam componentSp = mySearchParamRegistry.getActiveSearchParamByUrl(componentSpRef); 343 Validate.notNull( 344 componentSp, 345 "Misconfigured SP %s - failed to load component %s", 346 compositeSpName, 347 componentSpRef); 348 349 SearchParamSet<BaseResourceIndexedSearchParam> componentIndexedSearchParams = 350 extractCompositeComponentIndexData( 351 theValue, componentSp, expression, theWantLocalReferences, theCompositeSearchParam); 352 if (componentIndexedSearchParams.isEmpty()) { 353 // If any of the components are empty, no search can ever match. Short circuit, and bail out. 354 return; 355 } else { 356 e.addComponentIndexedSearchParams(componentSp, componentIndexedSearchParams); 357 } 358 } 359 360 // every component has data. Add it for indexing. 361 theParams.add(e); 362 } 363 364 /** 365 * Extract the subcomponent index data for a single component of a composite SP. 366 * 367 * @param theFocusElement the element to use as the root for sub-extraction 368 * @param theComponentSearchParam the active subcomponent SP for extraction 369 * @param theSubPathExpression the sub-expression to extract values from theFocusElement 370 * @param theWantLocalReferences flag for URI processing 371 * @param theCompositeSearchParam the parent composite SP 372 * @return the extracted index beans for theFocusElement 373 */ 374 @Nonnull 375 private SearchParamSet<BaseResourceIndexedSearchParam> extractCompositeComponentIndexData( 376 IBase theFocusElement, 377 RuntimeSearchParam theComponentSearchParam, 378 String theSubPathExpression, 379 boolean theWantLocalReferences, 380 RuntimeSearchParam theCompositeSearchParam) { 381 IExtractor componentExtractor = createExtractor(theComponentSearchParam, myResource); 382 SearchParamSet<BaseResourceIndexedSearchParam> componentIndexData = new SearchParamSet<>(); 383 384 extractSearchParam( 385 theComponentSearchParam, 386 theSubPathExpression, 387 theFocusElement, 388 componentExtractor, 389 componentIndexData, 390 theWantLocalReferences); 391 ourLog.trace( 392 "CompositeExtractor - extracted {} index values for {}", 393 componentIndexData.size(), 394 theComponentSearchParam.getName()); 395 396 return componentIndexData; 397 } 398 399 /** 400 * Is this an extractable composite SP? 401 * 402 * @param theSearchParam of type composite 403 * @return can we extract useful index data from this? 404 */ 405 private boolean isExtractableComposite(RuntimeSearchParam theSearchParam) { 406 // this is a composite SP 407 return RestSearchParameterTypeEnum.COMPOSITE.equals(theSearchParam.getParamType()) 408 && theSearchParam.getComponents().stream().noneMatch(this::isNotExtractableCompositeComponent); 409 } 410 411 private boolean isNotExtractableCompositeComponent(RuntimeSearchParam.Component c) { 412 RuntimeSearchParam componentSearchParam = mySearchParamRegistry.getActiveSearchParamByUrl(c.getReference()); 413 return // Does the sub-param link work? 414 componentSearchParam == null 415 || 416 // Is this the right type? 417 RestSearchParameterTypeEnum.COMPOSITE.equals(componentSearchParam.getParamType()) 418 || 419 420 // Bug workaround: the component expressions are null in the FhirContextSearchParamRegistry. We 421 // can't do anything with them. 422 c.getExpression() == null 423 || 424 425 // TODO mb Bug workaround: we don't support the %resource variable, but standard SPs on 426 // MolecularSequence use it. 427 // Skip them for now. 428 c.getExpression().contains("%resource"); 429 } 430 } 431 432 @Override 433 public SearchParamSet<ResourceIndexedComboStringUnique> extractSearchParamComboUnique( 434 String theResourceType, ResourceIndexedSearchParams theParams) { 435 SearchParamSet<ResourceIndexedComboStringUnique> retVal = new SearchParamSet<>(); 436 List<RuntimeSearchParam> runtimeComboUniqueParams = 437 mySearchParamRegistry.getActiveComboSearchParams(theResourceType, ComboSearchParamType.UNIQUE); 438 439 for (RuntimeSearchParam runtimeParam : runtimeComboUniqueParams) { 440 Set<ResourceIndexedComboStringUnique> comboUniqueParams = 441 createComboUniqueParam(theResourceType, theParams, runtimeParam); 442 retVal.addAll(comboUniqueParams); 443 } 444 return retVal; 445 } 446 447 private SearchParamSet<ResourceIndexedComboStringUnique> createComboUniqueParam( 448 String theResourceType, ResourceIndexedSearchParams theParams, RuntimeSearchParam theRuntimeParam) { 449 SearchParamSet<ResourceIndexedComboStringUnique> retVal = new SearchParamSet<>(); 450 Set<String> queryStringsToPopulate = 451 extractParameterCombinationsForComboParam(theParams, theResourceType, theRuntimeParam); 452 453 for (String nextQueryString : queryStringsToPopulate) { 454 ourLog.trace( 455 "Adding composite unique SP: {} on {} for {}", 456 nextQueryString, 457 theResourceType, 458 theRuntimeParam.getId()); 459 ResourceIndexedComboStringUnique uniqueParam = new ResourceIndexedComboStringUnique(); 460 uniqueParam.setIndexString(nextQueryString); 461 uniqueParam.setSearchParameterId(theRuntimeParam.getId()); 462 retVal.add(uniqueParam); 463 } 464 return retVal; 465 } 466 467 @Override 468 public SearchParamSet<ResourceIndexedComboTokenNonUnique> extractSearchParamComboNonUnique( 469 String theResourceType, ResourceIndexedSearchParams theParams) { 470 SearchParamSet<ResourceIndexedComboTokenNonUnique> retVal = new SearchParamSet<>(); 471 List<RuntimeSearchParam> runtimeComboNonUniqueParams = 472 mySearchParamRegistry.getActiveComboSearchParams(theResourceType, ComboSearchParamType.NON_UNIQUE); 473 474 for (RuntimeSearchParam runtimeParam : runtimeComboNonUniqueParams) { 475 Set<ResourceIndexedComboTokenNonUnique> comboNonUniqueParams = 476 createComboNonUniqueParam(theResourceType, theParams, runtimeParam); 477 retVal.addAll(comboNonUniqueParams); 478 } 479 return retVal; 480 } 481 482 private SearchParamSet<ResourceIndexedComboTokenNonUnique> createComboNonUniqueParam( 483 String theResourceType, ResourceIndexedSearchParams theParams, RuntimeSearchParam theRuntimeParam) { 484 SearchParamSet<ResourceIndexedComboTokenNonUnique> retVal = new SearchParamSet<>(); 485 Set<String> queryStringsToPopulate = 486 extractParameterCombinationsForComboParam(theParams, theResourceType, theRuntimeParam); 487 488 for (String nextQueryString : queryStringsToPopulate) { 489 ourLog.trace("Adding composite unique SP: {}", nextQueryString); 490 ResourceIndexedComboTokenNonUnique nonUniqueParam = new ResourceIndexedComboTokenNonUnique(); 491 nonUniqueParam.setPartitionSettings(myPartitionSettings); 492 nonUniqueParam.setIndexString(nextQueryString); 493 nonUniqueParam.setSearchParameterId(theRuntimeParam.getId()); 494 retVal.add(nonUniqueParam); 495 } 496 return retVal; 497 } 498 499 @Nonnull 500 private Set<String> extractParameterCombinationsForComboParam( 501 ResourceIndexedSearchParams theParams, String theResourceType, RuntimeSearchParam theParam) { 502 List<List<String>> partsChoices = new ArrayList<>(); 503 504 List<RuntimeSearchParam> compositeComponents = 505 JpaParamUtil.resolveComponentParameters(mySearchParamRegistry, theParam); 506 for (RuntimeSearchParam nextCompositeOf : compositeComponents) { 507 Collection<? extends BaseResourceIndexedSearchParam> paramsListForCompositePart = 508 findParameterIndexes(theParams, nextCompositeOf); 509 510 Collection<ResourceLink> linksForCompositePart = null; 511 switch (nextCompositeOf.getParamType()) { 512 case REFERENCE: 513 linksForCompositePart = theParams.myLinks; 514 break; 515 case NUMBER: 516 case DATE: 517 case STRING: 518 case TOKEN: 519 case QUANTITY: 520 case URI: 521 case SPECIAL: 522 case COMPOSITE: 523 case HAS: 524 break; 525 } 526 527 Collection<String> linksForCompositePartWantPaths = null; 528 switch (nextCompositeOf.getParamType()) { 529 case REFERENCE: 530 linksForCompositePartWantPaths = new HashSet<>(nextCompositeOf.getPathsSplit()); 531 break; 532 case NUMBER: 533 case DATE: 534 case STRING: 535 case TOKEN: 536 case QUANTITY: 537 case URI: 538 case SPECIAL: 539 case COMPOSITE: 540 case HAS: 541 break; 542 } 543 544 ArrayList<String> nextChoicesList = new ArrayList<>(); 545 partsChoices.add(nextChoicesList); 546 547 String key = UrlUtil.escapeUrlParam(nextCompositeOf.getName()); 548 if (paramsListForCompositePart != null) { 549 for (BaseResourceIndexedSearchParam nextParam : paramsListForCompositePart) { 550 IQueryParameterType nextParamAsClientParam = nextParam.toQueryParameterType(); 551 String value = nextParamAsClientParam.getValueAsQueryToken(myContext); 552 553 RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(theResourceType, key); 554 if (theParam.getComboSearchParamType() == ComboSearchParamType.NON_UNIQUE 555 && param != null 556 && param.getParamType() == RestSearchParameterTypeEnum.STRING) { 557 value = StringUtil.normalizeStringForSearchIndexing(value); 558 } 559 560 if (isNotBlank(value)) { 561 value = UrlUtil.escapeUrlParam(value); 562 nextChoicesList.add(key + "=" + value); 563 } 564 } 565 } 566 if (linksForCompositePart != null) { 567 for (ResourceLink nextLink : linksForCompositePart) { 568 if (linksForCompositePartWantPaths.contains(nextLink.getSourcePath())) { 569 assert isNotBlank(nextLink.getTargetResourceType()); 570 assert isNotBlank(nextLink.getTargetResourceId()); 571 String value = nextLink.getTargetResourceType() + "/" + nextLink.getTargetResourceId(); 572 if (isNotBlank(value)) { 573 value = UrlUtil.escapeUrlParam(value); 574 nextChoicesList.add(key + "=" + value); 575 } 576 } 577 } 578 } 579 } 580 581 return ResourceIndexedSearchParams.extractCompositeStringUniquesValueChains(theResourceType, partsChoices); 582 } 583 584 @Nullable 585 private Collection<? extends BaseResourceIndexedSearchParam> findParameterIndexes( 586 ResourceIndexedSearchParams theParams, RuntimeSearchParam nextCompositeOf) { 587 Collection<? extends BaseResourceIndexedSearchParam> paramsListForCompositePart = null; 588 switch (nextCompositeOf.getParamType()) { 589 case NUMBER: 590 paramsListForCompositePart = theParams.myNumberParams; 591 break; 592 case DATE: 593 paramsListForCompositePart = theParams.myDateParams; 594 break; 595 case STRING: 596 paramsListForCompositePart = theParams.myStringParams; 597 break; 598 case TOKEN: 599 paramsListForCompositePart = theParams.myTokenParams; 600 break; 601 case QUANTITY: 602 paramsListForCompositePart = theParams.myQuantityParams; 603 break; 604 case URI: 605 paramsListForCompositePart = theParams.myUriParams; 606 break; 607 case REFERENCE: 608 case SPECIAL: 609 case COMPOSITE: 610 case HAS: 611 break; 612 } 613 if (paramsListForCompositePart != null) { 614 paramsListForCompositePart = paramsListForCompositePart.stream() 615 .filter(t -> t.getParamName().equals(nextCompositeOf.getName())) 616 .collect(Collectors.toList()); 617 } 618 return paramsListForCompositePart; 619 } 620 621 @Override 622 public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens( 623 IBaseResource theResource, ISearchParamFilter theSearchParamFilter) { 624 IExtractor<BaseResourceIndexedSearchParam> extractor = createTokenExtractor(theResource); 625 return extractSearchParams( 626 theResource, extractor, RestSearchParameterTypeEnum.TOKEN, false, theSearchParamFilter); 627 } 628 629 @Override 630 public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens( 631 IBaseResource theResource, RuntimeSearchParam theSearchParam) { 632 IExtractor<BaseResourceIndexedSearchParam> extractor = createTokenExtractor(theResource); 633 SearchParamSet<BaseResourceIndexedSearchParam> setToPopulate = new SearchParamSet<>(); 634 extractSearchParam(theSearchParam, theResource, extractor, setToPopulate, false); 635 return setToPopulate; 636 } 637 638 private IExtractor<BaseResourceIndexedSearchParam> createTokenExtractor(IBaseResource theResource) { 639 String resourceTypeName = toRootTypeName(theResource); 640 String useSystem; 641 if (getContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2)) { 642 if (resourceTypeName.equals("ValueSet")) { 643 ca.uhn.fhir.model.dstu2.resource.ValueSet dstu2ValueSet = 644 (ca.uhn.fhir.model.dstu2.resource.ValueSet) theResource; 645 useSystem = dstu2ValueSet.getCodeSystem().getSystem(); 646 } else { 647 useSystem = null; 648 } 649 } else { 650 if (resourceTypeName.equals("CodeSystem")) { 651 useSystem = extractValueAsString(myCodeSystemUrlValueChild, theResource); 652 } else { 653 useSystem = null; 654 } 655 } 656 657 return new TokenExtractor(resourceTypeName, useSystem); 658 } 659 660 @Override 661 public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamSpecial( 662 IBaseResource theResource, ISearchParamFilter theSearchParamFilter) { 663 String resourceTypeName = toRootTypeName(theResource); 664 IExtractor<BaseResourceIndexedSearchParam> extractor = createSpecialExtractor(resourceTypeName); 665 return extractSearchParams( 666 theResource, extractor, RestSearchParameterTypeEnum.SPECIAL, false, theSearchParamFilter); 667 } 668 669 private IExtractor<BaseResourceIndexedSearchParam> createSpecialExtractor(String theResourceTypeName) { 670 return (params, searchParam, value, path, theWantLocalReferences) -> { 671 if (COORDS_INDEX_PATHS.contains(path)) { 672 addCoords_Position(theResourceTypeName, params, searchParam, value); 673 } 674 }; 675 } 676 677 private void addUnexpectedDatatypeWarning( 678 SearchParamSet<?> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath) { 679 String typeDesc = myContext.getElementDefinition(theValue.getClass()).getName(); 680 theParams.addWarning("Search param " + theSearchParam.getBase() + "#" + theSearchParam.getName() 681 + " is unable to index value of type " + typeDesc + " as a " 682 + theSearchParam.getParamType().name() + " at path: " + thePath); 683 } 684 685 @Override 686 public SearchParamSet<ResourceIndexedSearchParamUri> extractSearchParamUri( 687 IBaseResource theResource, ISearchParamFilter theSearchParamFilter) { 688 IExtractor<ResourceIndexedSearchParamUri> extractor = createUriExtractor(theResource); 689 return extractSearchParams( 690 theResource, extractor, RestSearchParameterTypeEnum.URI, false, theSearchParamFilter); 691 } 692 693 private IExtractor<ResourceIndexedSearchParamUri> createUriExtractor(IBaseResource theResource) { 694 return (params, searchParam, value, path, theWantLocalReferences) -> { 695 String nextType = toRootTypeName(value); 696 String resourceType = toRootTypeName(theResource); 697 switch (nextType) { 698 case "uri": 699 case "url": 700 case "oid": 701 case "sid": 702 case "uuid": 703 addUri_Uri(resourceType, params, searchParam, value); 704 break; 705 default: 706 addUnexpectedDatatypeWarning(params, searchParam, value, path); 707 break; 708 } 709 }; 710 } 711 712 @Override 713 public SearchParamSet<ResourceIndexedSearchParamDate> extractSearchParamDates( 714 IBaseResource theResource, ISearchParamFilter theSearchParamFilter) { 715 IExtractor<ResourceIndexedSearchParamDate> extractor = createDateExtractor(theResource); 716 return extractSearchParams(theResource, extractor, DATE, false, theSearchParamFilter); 717 } 718 719 private IExtractor<ResourceIndexedSearchParamDate> createDateExtractor(IBaseResource theResource) { 720 return new DateExtractor(theResource); 721 } 722 723 @Override 724 public Date extractDateFromResource(IBase theValue, String thePath) { 725 DateExtractor extractor = new DateExtractor("DateType"); 726 return extractor.get(theValue, thePath, false).getValueHigh(); 727 } 728 729 @Override 730 public SearchParamSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber( 731 IBaseResource theResource, ISearchParamFilter theSearchParamFilter) { 732 IExtractor<ResourceIndexedSearchParamNumber> extractor = createNumberExtractor(theResource); 733 return extractSearchParams( 734 theResource, extractor, RestSearchParameterTypeEnum.NUMBER, false, theSearchParamFilter); 735 } 736 737 private IExtractor<ResourceIndexedSearchParamNumber> createNumberExtractor(IBaseResource theResource) { 738 return (params, searchParam, value, path, theWantLocalReferences) -> { 739 String nextType = toRootTypeName(value); 740 String resourceType = toRootTypeName(theResource); 741 switch (nextType) { 742 case "Duration": 743 addNumber_Duration(resourceType, params, searchParam, value); 744 break; 745 case "Quantity": 746 addNumber_Quantity(resourceType, params, searchParam, value); 747 break; 748 case "integer": 749 case "positiveInt": 750 case "unsignedInt": 751 addNumber_Integer(resourceType, params, searchParam, value); 752 break; 753 case "decimal": 754 addNumber_Decimal(resourceType, params, searchParam, value); 755 break; 756 case "Range": 757 addNumber_Range(resourceType, params, searchParam, value); 758 break; 759 default: 760 addUnexpectedDatatypeWarning(params, searchParam, value, path); 761 break; 762 } 763 }; 764 } 765 766 @Override 767 public SearchParamSet<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity( 768 IBaseResource theResource, ISearchParamFilter theSearchParamFilter) { 769 IExtractor<ResourceIndexedSearchParamQuantity> extractor = createQuantityUnnormalizedExtractor(theResource); 770 return extractSearchParams( 771 theResource, extractor, RestSearchParameterTypeEnum.QUANTITY, false, theSearchParamFilter); 772 } 773 774 @Override 775 public SearchParamSet<ResourceIndexedSearchParamQuantityNormalized> extractSearchParamQuantityNormalized( 776 IBaseResource theResource, ISearchParamFilter theSearchParamFilter) { 777 IExtractor<ResourceIndexedSearchParamQuantityNormalized> extractor = 778 createQuantityNormalizedExtractor(theResource); 779 return extractSearchParams( 780 theResource, extractor, RestSearchParameterTypeEnum.QUANTITY, false, theSearchParamFilter); 781 } 782 783 @Nonnull 784 private IExtractor<? extends BaseResourceIndexedSearchParamQuantity> createQuantityExtractor( 785 IBaseResource theResource) { 786 IExtractor<? extends BaseResourceIndexedSearchParamQuantity> result; 787 if (myStorageSettings.getNormalizedQuantitySearchLevel().storageOrSearchSupported()) { 788 result = new MultiplexExtractor( 789 createQuantityUnnormalizedExtractor(theResource), createQuantityNormalizedExtractor(theResource)); 790 } else { 791 result = createQuantityUnnormalizedExtractor(theResource); 792 } 793 return result; 794 } 795 796 @Nonnull 797 private IExtractor<ResourceIndexedSearchParamQuantity> createQuantityUnnormalizedExtractor( 798 IBaseResource theResource) { 799 String resourceType = toRootTypeName(theResource); 800 return (params, searchParam, value, path, theWantLocalReferences) -> { 801 if (value.getClass().equals(myLocationPositionDefinition.getImplementingClass())) { 802 return; 803 } 804 805 String nextType = toRootTypeName(value); 806 switch (nextType) { 807 case "Quantity": 808 addQuantity_Quantity(resourceType, params, searchParam, value); 809 break; 810 case "Money": 811 addQuantity_Money(resourceType, params, searchParam, value); 812 break; 813 case "Range": 814 addQuantity_Range(resourceType, params, searchParam, value); 815 break; 816 default: 817 addUnexpectedDatatypeWarning(params, searchParam, value, path); 818 break; 819 } 820 }; 821 } 822 823 private IExtractor<ResourceIndexedSearchParamQuantityNormalized> createQuantityNormalizedExtractor( 824 IBaseResource theResource) { 825 826 return (params, searchParam, value, path, theWantLocalReferences) -> { 827 if (value.getClass().equals(myLocationPositionDefinition.getImplementingClass())) { 828 return; 829 } 830 831 String nextType = toRootTypeName(value); 832 String resourceType = toRootTypeName(theResource); 833 switch (nextType) { 834 case "Quantity": 835 addQuantity_QuantityNormalized(resourceType, params, searchParam, value); 836 break; 837 case "Money": 838 addQuantity_MoneyNormalized(resourceType, params, searchParam, value); 839 break; 840 case "Range": 841 addQuantity_RangeNormalized(resourceType, params, searchParam, value); 842 break; 843 default: 844 addUnexpectedDatatypeWarning(params, searchParam, value, path); 845 break; 846 } 847 }; 848 } 849 850 @Override 851 public SearchParamSet<ResourceIndexedSearchParamString> extractSearchParamStrings( 852 IBaseResource theResource, ISearchParamFilter theSearchParamFilter) { 853 IExtractor<ResourceIndexedSearchParamString> extractor = createStringExtractor(theResource); 854 855 return extractSearchParams( 856 theResource, extractor, RestSearchParameterTypeEnum.STRING, false, theSearchParamFilter); 857 } 858 859 private IExtractor<ResourceIndexedSearchParamString> createStringExtractor(IBaseResource theResource) { 860 String resourceType = toRootTypeName(theResource); 861 return (params, searchParam, value, path, theWantLocalReferences) -> { 862 if (value instanceof IPrimitiveType) { 863 IPrimitiveType<?> nextValue = (IPrimitiveType<?>) value; 864 String valueAsString = nextValue.getValueAsString(); 865 createStringIndexIfNotBlank(resourceType, params, searchParam, valueAsString); 866 return; 867 } 868 869 String nextType = toRootTypeName(value); 870 switch (nextType) { 871 case "HumanName": 872 addString_HumanName(resourceType, params, searchParam, value); 873 break; 874 case "Address": 875 addString_Address(resourceType, params, searchParam, value); 876 break; 877 case "ContactPoint": 878 addString_ContactPoint(resourceType, params, searchParam, value); 879 break; 880 case "Quantity": 881 addString_Quantity(resourceType, params, searchParam, value); 882 break; 883 case "Range": 884 addString_Range(resourceType, params, searchParam, value); 885 break; 886 case "Period": 887 // Condition.onset[x] can have a Period - Ignored for now 888 break; 889 default: 890 addUnexpectedDatatypeWarning(params, searchParam, value, path); 891 break; 892 } 893 }; 894 } 895 896 /** 897 * Override parent because we're using FHIRPath here 898 */ 899 @Override 900 public List<IBase> extractValues(String thePaths, IBase theResource) { 901 List<IBase> values = new ArrayList<>(); 902 if (isNotBlank(thePaths)) { 903 String[] nextPathsSplit = split(thePaths); 904 for (String nextPath : nextPathsSplit) { 905 List<? extends IBase> allValues; 906 907 // This path is hard to parse and isn't likely to produce anything useful anyway 908 if (myContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU2) 909 && nextPath.equals("Bundle.entry.resource(0)")) { 910 continue; 911 } 912 913 nextPath = trim(nextPath); 914 IValueExtractor allValuesFunc = getPathValueExtractor(theResource, nextPath); 915 try { 916 allValues = allValuesFunc.get(); 917 } catch (Exception e) { 918 String msg = getContext() 919 .getLocalizer() 920 .getMessage(BaseSearchParamExtractor.class, "failedToExtractPaths", nextPath, e.toString()); 921 throw new InternalErrorException(Msg.code(504) + msg, e); 922 } 923 924 values.addAll(allValues); 925 } 926 927 for (int i = 0; i < values.size(); i++) { 928 IBase nextObject = values.get(i); 929 if (nextObject instanceof IBaseExtension) { 930 IBaseExtension nextExtension = (IBaseExtension) nextObject; 931 nextObject = nextExtension.getValue(); 932 values.set(i, nextObject); 933 } 934 } 935 } 936 937 return values; 938 } 939 940 protected FhirContext getContext() { 941 return myContext; 942 } 943 944 @VisibleForTesting 945 public void setContext(FhirContext theContext) { 946 myContext = theContext; 947 } 948 949 protected StorageSettings getStorageSettings() { 950 return myStorageSettings; 951 } 952 953 @VisibleForTesting 954 public void setStorageSettings(StorageSettings theStorageSettings) { 955 myStorageSettings = theStorageSettings; 956 } 957 958 @VisibleForTesting 959 public void setSearchParamRegistry(ISearchParamRegistry theSearchParamRegistry) { 960 mySearchParamRegistry = theSearchParamRegistry; 961 } 962 963 private Collection<RuntimeSearchParam> getSearchParams(IBaseResource theResource) { 964 RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); 965 Collection<RuntimeSearchParam> retVal = 966 mySearchParamRegistry.getActiveSearchParams(def.getName()).values(); 967 List<RuntimeSearchParam> defaultList = Collections.emptyList(); 968 retVal = ObjectUtils.defaultIfNull(retVal, defaultList); 969 return retVal; 970 } 971 972 private void addQuantity_Quantity( 973 String theResourceType, 974 Set<ResourceIndexedSearchParamQuantity> theParams, 975 RuntimeSearchParam theSearchParam, 976 IBase theValue) { 977 Optional<IPrimitiveType<BigDecimal>> valueField = 978 myQuantityValueValueChild.getAccessor().getFirstValueOrNull(theValue); 979 if (valueField.isPresent() && valueField.get().getValue() != null) { 980 BigDecimal nextValueValue = valueField.get().getValue(); 981 String system = extractValueAsString(myQuantitySystemValueChild, theValue); 982 String code = extractValueAsString(myQuantityCodeValueChild, theValue); 983 984 ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity( 985 myPartitionSettings, theResourceType, theSearchParam.getName(), nextValueValue, system, code); 986 987 theParams.add(nextEntity); 988 } 989 } 990 991 private void addQuantity_QuantityNormalized( 992 String theResourceType, 993 Set<ResourceIndexedSearchParamQuantityNormalized> theParams, 994 RuntimeSearchParam theSearchParam, 995 IBase theValue) { 996 Optional<IPrimitiveType<BigDecimal>> valueField = 997 myQuantityValueValueChild.getAccessor().getFirstValueOrNull(theValue); 998 if (valueField.isPresent() && valueField.get().getValue() != null) { 999 BigDecimal nextValueValue = valueField.get().getValue(); 1000 String system = extractValueAsString(myQuantitySystemValueChild, theValue); 1001 String code = extractValueAsString(myQuantityCodeValueChild, theValue); 1002 1003 // -- convert the value/unit to the canonical form if any 1004 Pair canonicalForm = UcumServiceUtil.getCanonicalForm(system, nextValueValue, code); 1005 if (canonicalForm != null) { 1006 double canonicalValue = 1007 Double.parseDouble(canonicalForm.getValue().asDecimal()); 1008 String canonicalUnits = canonicalForm.getCode(); 1009 ResourceIndexedSearchParamQuantityNormalized nextEntity = 1010 new ResourceIndexedSearchParamQuantityNormalized( 1011 myPartitionSettings, 1012 theResourceType, 1013 theSearchParam.getName(), 1014 canonicalValue, 1015 system, 1016 canonicalUnits); 1017 theParams.add(nextEntity); 1018 } 1019 } 1020 } 1021 1022 private void addQuantity_Money( 1023 String theResourceType, 1024 Set<ResourceIndexedSearchParamQuantity> theParams, 1025 RuntimeSearchParam theSearchParam, 1026 IBase theValue) { 1027 Optional<IPrimitiveType<BigDecimal>> valueField = 1028 myMoneyValueChild.getAccessor().getFirstValueOrNull(theValue); 1029 if (valueField.isPresent() && valueField.get().getValue() != null) { 1030 BigDecimal nextValueValue = valueField.get().getValue(); 1031 1032 String nextValueString = "urn:iso:std:iso:4217"; 1033 String nextValueCode = extractValueAsString(myMoneyCurrencyChild, theValue); 1034 String searchParamName = theSearchParam.getName(); 1035 1036 ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity( 1037 myPartitionSettings, 1038 theResourceType, 1039 searchParamName, 1040 nextValueValue, 1041 nextValueString, 1042 nextValueCode); 1043 theParams.add(nextEntity); 1044 } 1045 } 1046 1047 private void addQuantity_MoneyNormalized( 1048 String theResourceType, 1049 Set<ResourceIndexedSearchParamQuantityNormalized> theParams, 1050 RuntimeSearchParam theSearchParam, 1051 IBase theValue) { 1052 Optional<IPrimitiveType<BigDecimal>> valueField = 1053 myMoneyValueChild.getAccessor().getFirstValueOrNull(theValue); 1054 if (valueField.isPresent() && valueField.get().getValue() != null) { 1055 BigDecimal nextValueValue = valueField.get().getValue(); 1056 1057 String nextValueString = "urn:iso:std:iso:4217"; 1058 String nextValueCode = extractValueAsString(myMoneyCurrencyChild, theValue); 1059 String searchParamName = theSearchParam.getName(); 1060 1061 ResourceIndexedSearchParamQuantityNormalized nextEntityNormalized = 1062 new ResourceIndexedSearchParamQuantityNormalized( 1063 myPartitionSettings, 1064 theResourceType, 1065 searchParamName, 1066 nextValueValue.doubleValue(), 1067 nextValueString, 1068 nextValueCode); 1069 theParams.add(nextEntityNormalized); 1070 } 1071 } 1072 1073 private void addQuantity_Range( 1074 String theResourceType, 1075 Set<ResourceIndexedSearchParamQuantity> theParams, 1076 RuntimeSearchParam theSearchParam, 1077 IBase theValue) { 1078 Optional<IBase> low = myRangeLowValueChild.getAccessor().getFirstValueOrNull(theValue); 1079 low.ifPresent(theIBase -> addQuantity_Quantity(theResourceType, theParams, theSearchParam, theIBase)); 1080 1081 Optional<IBase> high = myRangeHighValueChild.getAccessor().getFirstValueOrNull(theValue); 1082 high.ifPresent(theIBase -> addQuantity_Quantity(theResourceType, theParams, theSearchParam, theIBase)); 1083 } 1084 1085 private void addQuantity_RangeNormalized( 1086 String theResourceType, 1087 Set<ResourceIndexedSearchParamQuantityNormalized> theParams, 1088 RuntimeSearchParam theSearchParam, 1089 IBase theValue) { 1090 Optional<IBase> low = myRangeLowValueChild.getAccessor().getFirstValueOrNull(theValue); 1091 low.ifPresent(theIBase -> addQuantity_QuantityNormalized(theResourceType, theParams, theSearchParam, theIBase)); 1092 1093 Optional<IBase> high = myRangeHighValueChild.getAccessor().getFirstValueOrNull(theValue); 1094 high.ifPresent( 1095 theIBase -> addQuantity_QuantityNormalized(theResourceType, theParams, theSearchParam, theIBase)); 1096 } 1097 1098 @SuppressWarnings("unchecked") 1099 private void addToken_Identifier( 1100 String theResourceType, 1101 Set<BaseResourceIndexedSearchParam> theParams, 1102 RuntimeSearchParam theSearchParam, 1103 IBase theValue) { 1104 String system = extractValueAsString(myIdentifierSystemValueChild, theValue); 1105 String value = extractValueAsString(myIdentifierValueValueChild, theValue); 1106 if (isNotBlank(value)) { 1107 createTokenIndexIfNotBlankAndAdd(theResourceType, theParams, theSearchParam, system, value); 1108 1109 boolean indexIdentifierType = myStorageSettings.isIndexIdentifierOfType(); 1110 if (indexIdentifierType) { 1111 Optional<IBase> type = myIdentifierTypeValueChild.getAccessor().getFirstValueOrNull(theValue); 1112 if (type.isPresent()) { 1113 List<IBase> codings = 1114 myCodeableConceptCodingValueChild.getAccessor().getValues(type.get()); 1115 for (IBase nextCoding : codings) { 1116 1117 String typeSystem = myCodingSystemValueChild 1118 .getAccessor() 1119 .getFirstValueOrNull(nextCoding) 1120 .map(t -> ((IPrimitiveType<String>) t).getValue()) 1121 .orElse(null); 1122 String typeValue = myCodingCodeValueChild 1123 .getAccessor() 1124 .getFirstValueOrNull(nextCoding) 1125 .map(t -> ((IPrimitiveType<String>) t).getValue()) 1126 .orElse(null); 1127 if (isNotBlank(typeSystem) && isNotBlank(typeValue)) { 1128 String paramName = theSearchParam.getName() + Constants.PARAMQUALIFIER_TOKEN_OF_TYPE; 1129 ResourceIndexedSearchParamToken token = createTokenIndexIfNotBlank( 1130 theResourceType, typeSystem, typeValue + "|" + value, paramName); 1131 if (token != null) { 1132 theParams.add(token); 1133 } 1134 } 1135 } 1136 } 1137 } 1138 } 1139 } 1140 1141 protected boolean shouldIndexTextComponentOfToken(RuntimeSearchParam theSearchParam) { 1142 return tokenTextIndexingEnabledForSearchParam(myStorageSettings, theSearchParam); 1143 } 1144 1145 private void addToken_CodeableConcept( 1146 String theResourceType, 1147 Set<BaseResourceIndexedSearchParam> theParams, 1148 RuntimeSearchParam theSearchParam, 1149 IBase theValue) { 1150 List<IBase> codings = getCodingsFromCodeableConcept(theValue); 1151 for (IBase nextCoding : codings) { 1152 addToken_Coding(theResourceType, theParams, theSearchParam, nextCoding); 1153 } 1154 1155 if (shouldIndexTextComponentOfToken(theSearchParam)) { 1156 String text = getDisplayTextFromCodeableConcept(theValue); 1157 if (isNotBlank(text)) { 1158 createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, text); 1159 } 1160 } 1161 } 1162 1163 @Override 1164 public List<IBase> getCodingsFromCodeableConcept(IBase theValue) { 1165 String nextType = BaseSearchParamExtractor.this.toRootTypeName(theValue); 1166 if ("CodeableConcept".equals(nextType)) { 1167 return myCodeableConceptCodingValueChild.getAccessor().getValues(theValue); 1168 } else { 1169 return null; 1170 } 1171 } 1172 1173 @Override 1174 public String getDisplayTextFromCodeableConcept(IBase theValue) { 1175 String nextType = BaseSearchParamExtractor.this.toRootTypeName(theValue); 1176 if ("CodeableConcept".equals(nextType)) { 1177 return extractValueAsString(myCodeableConceptTextValueChild, theValue); 1178 } else { 1179 return null; 1180 } 1181 } 1182 1183 private void addToken_Coding( 1184 String theResourceType, 1185 Set<BaseResourceIndexedSearchParam> theParams, 1186 RuntimeSearchParam theSearchParam, 1187 IBase theValue) { 1188 ResourceIndexedSearchParamToken resourceIndexedSearchParamToken = 1189 createSearchParamForCoding(theResourceType, theSearchParam, theValue); 1190 if (resourceIndexedSearchParamToken != null) { 1191 theParams.add(resourceIndexedSearchParamToken); 1192 } 1193 1194 if (shouldIndexTextComponentOfToken(theSearchParam)) { 1195 String text = getDisplayTextForCoding(theValue); 1196 createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, text); 1197 } 1198 } 1199 1200 @Override 1201 public ResourceIndexedSearchParamToken createSearchParamForCoding( 1202 String theResourceType, RuntimeSearchParam theSearchParam, IBase theValue) { 1203 String nextType = BaseSearchParamExtractor.this.toRootTypeName(theValue); 1204 if ("Coding".equals(nextType)) { 1205 String system = extractValueAsString(myCodingSystemValueChild, theValue); 1206 String code = extractValueAsString(myCodingCodeValueChild, theValue); 1207 return createTokenIndexIfNotBlank(theResourceType, system, code, theSearchParam.getName()); 1208 } else { 1209 return null; 1210 } 1211 } 1212 1213 @Override 1214 public String getDisplayTextForCoding(IBase theValue) { 1215 String nextType = BaseSearchParamExtractor.this.toRootTypeName(theValue); 1216 if ("Coding".equals(nextType)) { 1217 return extractValueAsString(myCodingDisplayValueChild, theValue); 1218 } else { 1219 return null; 1220 } 1221 } 1222 1223 private void addToken_ContactPoint( 1224 String theResourceType, 1225 Set<BaseResourceIndexedSearchParam> theParams, 1226 RuntimeSearchParam theSearchParam, 1227 IBase theValue) { 1228 String system = extractValueAsString(myContactPointSystemValueChild, theValue); 1229 String value = extractValueAsString(myContactPointValueValueChild, theValue); 1230 createTokenIndexIfNotBlankAndAdd(theResourceType, theParams, theSearchParam, system, value); 1231 } 1232 1233 private void addToken_CodeableReference( 1234 String theResourceType, 1235 Set<BaseResourceIndexedSearchParam> theParams, 1236 RuntimeSearchParam theSearchParam, 1237 IBase theValue) { 1238 Optional<IBase> conceptOpt = myCodeableReferenceConcept.getAccessor().getFirstValueOrNull(theValue); 1239 conceptOpt.ifPresent(concept -> addToken_CodeableConcept(theResourceType, theParams, theSearchParam, concept)); 1240 } 1241 1242 private void addToken_PatientCommunication( 1243 String theResourceType, 1244 Set<BaseResourceIndexedSearchParam> theParams, 1245 RuntimeSearchParam theSearchParam, 1246 IBase theValue) { 1247 List<IBase> values = 1248 myPatientCommunicationLanguageValueChild.getAccessor().getValues(theValue); 1249 for (IBase next : values) { 1250 addToken_CodeableConcept(theResourceType, theParams, theSearchParam, next); 1251 } 1252 } 1253 1254 private void addToken_CapabilityStatementRestSecurity( 1255 String theResourceType, 1256 Set<BaseResourceIndexedSearchParam> theParams, 1257 RuntimeSearchParam theSearchParam, 1258 IBase theValue) { 1259 List<IBase> values = 1260 myCapabilityStatementRestSecurityServiceValueChild.getAccessor().getValues(theValue); 1261 for (IBase nextValue : values) { 1262 addToken_CodeableConcept(theResourceType, theParams, theSearchParam, nextValue); 1263 } 1264 } 1265 1266 private void addDate_Period( 1267 String theResourceType, 1268 Set<ResourceIndexedSearchParamDate> theParams, 1269 RuntimeSearchParam theSearchParam, 1270 IBase theValue) { 1271 Date start = extractValueAsDate(myPeriodStartValueChild, theValue); 1272 String startAsString = extractValueAsString(myPeriodStartValueChild, theValue); 1273 Date end = extractValueAsDate(myPeriodEndValueChild, theValue); 1274 String endAsString = extractValueAsString(myPeriodEndValueChild, theValue); 1275 1276 if (start != null || end != null) { 1277 1278 if (start == null) { 1279 start = myStorageSettings.getPeriodIndexStartOfTime().getValue(); 1280 startAsString = myStorageSettings.getPeriodIndexStartOfTime().getValueAsString(); 1281 } 1282 if (end == null) { 1283 end = myStorageSettings.getPeriodIndexEndOfTime().getValue(); 1284 endAsString = myStorageSettings.getPeriodIndexEndOfTime().getValueAsString(); 1285 } 1286 1287 ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate( 1288 myPartitionSettings, 1289 theResourceType, 1290 theSearchParam.getName(), 1291 start, 1292 startAsString, 1293 end, 1294 endAsString, 1295 startAsString); 1296 theParams.add(nextEntity); 1297 } 1298 } 1299 1300 private void addDate_Timing( 1301 String theResourceType, 1302 Set<ResourceIndexedSearchParamDate> theParams, 1303 RuntimeSearchParam theSearchParam, 1304 IBase theValue) { 1305 List<IPrimitiveType<Date>> values = extractValuesAsFhirDates(myTimingEventValueChild, theValue); 1306 1307 TreeSet<Date> dates = new TreeSet<>(); 1308 TreeSet<String> dateStrings = new TreeSet<>(); 1309 String firstValue = null; 1310 String finalValue = null; 1311 for (IPrimitiveType<Date> nextEvent : values) { 1312 if (nextEvent.getValue() != null) { 1313 dates.add(nextEvent.getValue()); 1314 if (firstValue == null) { 1315 firstValue = nextEvent.getValueAsString(); 1316 } 1317 finalValue = nextEvent.getValueAsString(); 1318 } 1319 } 1320 1321 Optional<IBase> repeat = myTimingRepeatValueChild.getAccessor().getFirstValueOrNull(theValue); 1322 if (repeat.isPresent()) { 1323 Optional<IBase> bounds = 1324 myTimingRepeatBoundsValueChild.getAccessor().getFirstValueOrNull(repeat.get()); 1325 if (bounds.isPresent()) { 1326 String boundsType = toRootTypeName(bounds.get()); 1327 if ("Period".equals(boundsType)) { 1328 Date start = extractValueAsDate(myPeriodStartValueChild, bounds.get()); 1329 Date end = extractValueAsDate(myPeriodEndValueChild, bounds.get()); 1330 String endString = extractValueAsString(myPeriodEndValueChild, bounds.get()); 1331 dates.add(start); 1332 dates.add(end); 1333 // TODO Check if this logic is valid. Does the start of the first period indicate a lower bound?? 1334 if (firstValue == null) { 1335 firstValue = extractValueAsString(myPeriodStartValueChild, bounds.get()); 1336 } 1337 finalValue = endString; 1338 } 1339 } 1340 } 1341 1342 if (!dates.isEmpty()) { 1343 ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate( 1344 myPartitionSettings, 1345 theResourceType, 1346 theSearchParam.getName(), 1347 dates.first(), 1348 firstValue, 1349 dates.last(), 1350 finalValue, 1351 firstValue); 1352 theParams.add(nextEntity); 1353 } 1354 } 1355 1356 private void addNumber_Duration( 1357 String theResourceType, 1358 Set<ResourceIndexedSearchParamNumber> theParams, 1359 RuntimeSearchParam theSearchParam, 1360 IBase theValue) { 1361 String system = extractValueAsString(myDurationSystemValueChild, theValue); 1362 String code = extractValueAsString(myDurationCodeValueChild, theValue); 1363 BigDecimal value = extractValueAsBigDecimal(myDurationValueValueChild, theValue); 1364 if (value != null) { 1365 value = normalizeQuantityContainingTimeUnitsIntoDaysForNumberParam(system, code, value); 1366 ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber( 1367 myPartitionSettings, theResourceType, theSearchParam.getName(), value); 1368 theParams.add(nextEntity); 1369 } 1370 } 1371 1372 private void addNumber_Quantity( 1373 String theResourceType, 1374 Set<ResourceIndexedSearchParamNumber> theParams, 1375 RuntimeSearchParam theSearchParam, 1376 IBase theValue) { 1377 BigDecimal value = extractValueAsBigDecimal(myQuantityValueValueChild, theValue); 1378 if (value != null) { 1379 String system = extractValueAsString(myQuantitySystemValueChild, theValue); 1380 String code = extractValueAsString(myQuantityCodeValueChild, theValue); 1381 value = normalizeQuantityContainingTimeUnitsIntoDaysForNumberParam(system, code, value); 1382 ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber( 1383 myPartitionSettings, theResourceType, theSearchParam.getName(), value); 1384 theParams.add(nextEntity); 1385 } 1386 } 1387 1388 @SuppressWarnings("unchecked") 1389 private void addNumber_Range( 1390 String theResourceType, 1391 Set<ResourceIndexedSearchParamNumber> theParams, 1392 RuntimeSearchParam theSearchParam, 1393 IBase theValue) { 1394 Optional<IBase> low = myRangeLowValueChild.getAccessor().getFirstValueOrNull(theValue); 1395 low.ifPresent(value -> addNumber_Quantity(theResourceType, theParams, theSearchParam, value)); 1396 1397 Optional<IBase> high = myRangeHighValueChild.getAccessor().getFirstValueOrNull(theValue); 1398 high.ifPresent(value -> addNumber_Quantity(theResourceType, theParams, theSearchParam, value)); 1399 } 1400 1401 @SuppressWarnings("unchecked") 1402 private void addNumber_Integer( 1403 String theResourceType, 1404 Set<ResourceIndexedSearchParamNumber> theParams, 1405 RuntimeSearchParam theSearchParam, 1406 IBase theValue) { 1407 IPrimitiveType<Integer> value = (IPrimitiveType<Integer>) theValue; 1408 if (value.getValue() != null) { 1409 BigDecimal valueDecimal = new BigDecimal(value.getValue()); 1410 ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber( 1411 myPartitionSettings, theResourceType, theSearchParam.getName(), valueDecimal); 1412 theParams.add(nextEntity); 1413 } 1414 } 1415 1416 @SuppressWarnings("unchecked") 1417 private void addNumber_Decimal( 1418 String theResourceType, 1419 Set<ResourceIndexedSearchParamNumber> theParams, 1420 RuntimeSearchParam theSearchParam, 1421 IBase theValue) { 1422 IPrimitiveType<BigDecimal> value = (IPrimitiveType<BigDecimal>) theValue; 1423 if (value.getValue() != null) { 1424 BigDecimal valueDecimal = value.getValue(); 1425 ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber( 1426 myPartitionSettings, theResourceType, theSearchParam.getName(), valueDecimal); 1427 theParams.add(nextEntity); 1428 } 1429 } 1430 1431 private void addCoords_Position( 1432 String theResourceType, 1433 SearchParamSet<BaseResourceIndexedSearchParam> theParams, 1434 RuntimeSearchParam theSearchParam, 1435 IBase theValue) { 1436 BigDecimal latitude = null; 1437 BigDecimal longitude = null; 1438 1439 if (theValue instanceof org.hl7.fhir.dstu3.model.Location.LocationPositionComponent) { 1440 org.hl7.fhir.dstu3.model.Location.LocationPositionComponent value = 1441 (org.hl7.fhir.dstu3.model.Location.LocationPositionComponent) theValue; 1442 latitude = value.getLatitude(); 1443 longitude = value.getLongitude(); 1444 } else if (theValue instanceof org.hl7.fhir.r4.model.Location.LocationPositionComponent) { 1445 org.hl7.fhir.r4.model.Location.LocationPositionComponent value = 1446 (org.hl7.fhir.r4.model.Location.LocationPositionComponent) theValue; 1447 latitude = value.getLatitude(); 1448 longitude = value.getLongitude(); 1449 } else if (theValue instanceof org.hl7.fhir.r5.model.Location.LocationPositionComponent) { 1450 org.hl7.fhir.r5.model.Location.LocationPositionComponent value = 1451 (org.hl7.fhir.r5.model.Location.LocationPositionComponent) theValue; 1452 latitude = value.getLatitude(); 1453 longitude = value.getLongitude(); 1454 } 1455 // We only accept coordinates when both are present 1456 if (latitude != null && longitude != null) { 1457 double normalizedLatitude = GeopointNormalizer.normalizeLatitude(latitude.doubleValue()); 1458 double normalizedLongitude = GeopointNormalizer.normalizeLongitude(longitude.doubleValue()); 1459 ResourceIndexedSearchParamCoords nextEntity = new ResourceIndexedSearchParamCoords( 1460 myPartitionSettings, 1461 theResourceType, 1462 theSearchParam.getName(), 1463 normalizedLatitude, 1464 normalizedLongitude); 1465 theParams.add(nextEntity); 1466 } 1467 } 1468 1469 private void addString_HumanName( 1470 String theResourceType, 1471 Set<ResourceIndexedSearchParamString> theParams, 1472 RuntimeSearchParam theSearchParam, 1473 IBase theValue) { 1474 List<BaseRuntimeChildDefinition> myHumanNameChildren = Arrays.asList( 1475 myHumanNameFamilyValueChild, 1476 myHumanNameGivenValueChild, 1477 myHumanNameTextValueChild, 1478 myHumanNamePrefixValueChild, 1479 myHumanNameSuffixValueChild); 1480 for (BaseRuntimeChildDefinition theChild : myHumanNameChildren) { 1481 List<String> indices = extractValuesAsStrings(theChild, theValue); 1482 for (String next : indices) { 1483 createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, next); 1484 } 1485 } 1486 } 1487 1488 private void addString_Quantity( 1489 String theResourceType, 1490 Set<ResourceIndexedSearchParamString> theParams, 1491 RuntimeSearchParam theSearchParam, 1492 IBase theValue) { 1493 BigDecimal value = extractValueAsBigDecimal(myQuantityValueValueChild, theValue); 1494 if (value != null) { 1495 createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, value.toPlainString()); 1496 } 1497 } 1498 1499 private void addString_Range( 1500 String theResourceType, 1501 Set<ResourceIndexedSearchParamString> theParams, 1502 RuntimeSearchParam theSearchParam, 1503 IBase theValue) { 1504 Optional<IBase> value = myRangeLowValueChild.getAccessor().getFirstValueOrNull(theValue); 1505 value.ifPresent(t -> addString_Quantity(theResourceType, theParams, theSearchParam, t)); 1506 } 1507 1508 private void addString_ContactPoint( 1509 String theResourceType, 1510 Set<ResourceIndexedSearchParamString> theParams, 1511 RuntimeSearchParam theSearchParam, 1512 IBase theValue) { 1513 1514 String value = extractValueAsString(myContactPointValueValueChild, theValue); 1515 if (isNotBlank(value)) { 1516 createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, value); 1517 } 1518 } 1519 1520 private void addString_Address( 1521 String theResourceType, 1522 Set<ResourceIndexedSearchParamString> theParams, 1523 RuntimeSearchParam theSearchParam, 1524 IBase theValue) { 1525 1526 List<String> allNames = new ArrayList<>(extractValuesAsStrings(myAddressLineValueChild, theValue)); 1527 1528 String city = extractValueAsString(myAddressCityValueChild, theValue); 1529 if (isNotBlank(city)) { 1530 allNames.add(city); 1531 } 1532 1533 String district = extractValueAsString(myAddressDistrictValueChild, theValue); 1534 if (isNotBlank(district)) { 1535 allNames.add(district); 1536 } 1537 1538 String state = extractValueAsString(myAddressStateValueChild, theValue); 1539 if (isNotBlank(state)) { 1540 allNames.add(state); 1541 } 1542 1543 String country = extractValueAsString(myAddressCountryValueChild, theValue); 1544 if (isNotBlank(country)) { 1545 allNames.add(country); 1546 } 1547 1548 String postalCode = extractValueAsString(myAddressPostalCodeValueChild, theValue); 1549 if (isNotBlank(postalCode)) { 1550 allNames.add(postalCode); 1551 } 1552 1553 for (String nextName : allNames) { 1554 createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, nextName); 1555 } 1556 } 1557 1558 /** 1559 * Ignore any of the Resource-level search params. This is kind of awkward, but here is why 1560 * we do it: 1561 * <p> 1562 * The ReadOnlySearchParamCache supplies these params, and they have paths associated with 1563 * them. E.g. HAPI's SearchParamRegistryImpl will know about the _id search parameter and 1564 * assigns it the path "Resource.id". All of these parameters have indexing code paths in the 1565 * server that don't rely on the existence of the SearchParameter. For example, we have a 1566 * dedicated column on ResourceTable that handles the _id parameter. 1567 * <p> 1568 * Until 6.2.0 the FhirPath evaluator didn't actually resolve any values for these paths 1569 * that started with Resource instead of the actual resource name, so it never actually 1570 * made a difference that these parameters existed because they'd never actually result 1571 * in any index rows. In 6.4.0 that bug was fixed in the core FhirPath engine. We don't 1572 * want that fix to result in pointless index rows for things like _id and _tag, so we 1573 * ignore them here. 1574 * <p> 1575 * Note that you can still create a search parameter that includes a path like 1576 * "meta.tag" if you really need to create an SP that actually does index _tag. This 1577 * is needed if you want to search for tags in <code>INLINE</code> tag storage mode. 1578 * This is the only way you could actually specify a FhirPath expression for those 1579 * prior to 6.2.0 so this isn't a breaking change. 1580 */ 1581 <T> SearchParamSet<T> extractSearchParams( 1582 IBaseResource theResource, 1583 IExtractor<T> theExtractor, 1584 RestSearchParameterTypeEnum theSearchParamType, 1585 boolean theWantLocalReferences, 1586 ISearchParamFilter theSearchParamFilter) { 1587 SearchParamSet<T> retVal = new SearchParamSet<>(); 1588 1589 Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource); 1590 1591 int preFilterSize = searchParams.size(); 1592 Collection<RuntimeSearchParam> filteredSearchParams = theSearchParamFilter.filterSearchParams(searchParams); 1593 assert filteredSearchParams.size() == preFilterSize || searchParams != filteredSearchParams; 1594 1595 cleanUpContainedResourceReferences(theResource, theSearchParamType, filteredSearchParams); 1596 1597 for (RuntimeSearchParam nextSpDef : filteredSearchParams) { 1598 if (nextSpDef.getParamType() != theSearchParamType) { 1599 continue; 1600 } 1601 1602 // See the method javadoc for an explanation of this 1603 if (RuntimeSearchParamHelper.isResourceLevel(nextSpDef)) { 1604 continue; 1605 } 1606 1607 extractSearchParam(nextSpDef, theResource, theExtractor, retVal, theWantLocalReferences); 1608 } 1609 return retVal; 1610 } 1611 1612 /** 1613 * Helper function to determine if a set of SPs for a resource uses a resolve as part of its fhir path. 1614 */ 1615 private boolean anySearchParameterUsesResolve( 1616 Collection<RuntimeSearchParam> searchParams, RestSearchParameterTypeEnum theSearchParamType) { 1617 return searchParams.stream() 1618 .filter(param -> param.getParamType() != theSearchParamType) 1619 .map(RuntimeSearchParam::getPath) 1620 .filter(Objects::nonNull) 1621 .anyMatch(path -> path.contains("resolve")); 1622 } 1623 1624 /** 1625 * HAPI FHIR Reference objects (e.g. {@link org.hl7.fhir.r4.model.Reference}) can hold references either by text 1626 * (e.g. "#3") or by resource (e.g. "new Reference(patientInstance)"). The FHIRPath evaluator only understands the 1627 * first way, so if there is any chance of the FHIRPath evaluator needing to descend across references, we 1628 * have to assign values to those references before indexing. 1629 * <p> 1630 * Doing this cleanup isn't hugely expensive, but it's not completely free either so we only do it 1631 * if we think there's actually a chance 1632 */ 1633 private void cleanUpContainedResourceReferences( 1634 IBaseResource theResource, 1635 RestSearchParameterTypeEnum theSearchParamType, 1636 Collection<RuntimeSearchParam> searchParams) { 1637 boolean havePathWithResolveExpression = myStorageSettings.isIndexOnContainedResources() 1638 || anySearchParameterUsesResolve(searchParams, theSearchParamType); 1639 1640 if (havePathWithResolveExpression && myContext.getParserOptions().isAutoContainReferenceTargetsWithNoId()) { 1641 // TODO GGG/JA: At this point, if the Task.basedOn.reference.resource does _not_ have an ID, we will attempt 1642 // to contain it internally. Wild 1643 myContext 1644 .newTerser() 1645 .containResources( 1646 theResource, 1647 FhirTerser.OptionsEnum.MODIFY_RESOURCE, 1648 FhirTerser.OptionsEnum.STORE_AND_REUSE_RESULTS); 1649 } 1650 } 1651 1652 /** 1653 * extract for normal SP 1654 */ 1655 @VisibleForTesting 1656 public <T> void extractSearchParam( 1657 RuntimeSearchParam theSearchParameterDef, 1658 IBase theResource, 1659 IExtractor<T> theExtractor, 1660 SearchParamSet<T> theSetToPopulate, 1661 boolean theWantLocalReferences) { 1662 String nextPathUnsplit = theSearchParameterDef.getPath(); 1663 extractSearchParam( 1664 theSearchParameterDef, 1665 nextPathUnsplit, 1666 theResource, 1667 theExtractor, 1668 theSetToPopulate, 1669 theWantLocalReferences); 1670 } 1671 1672 /** 1673 * extract for SP, but with possibly different expression. 1674 * Allows composite SPs to use sub-paths. 1675 */ 1676 private <T> void extractSearchParam( 1677 RuntimeSearchParam theSearchParameterDef, 1678 String thePathExpression, 1679 IBase theResource, 1680 IExtractor<T> theExtractor, 1681 SearchParamSet<T> theSetToPopulate, 1682 boolean theWantLocalReferences) { 1683 if (isBlank(thePathExpression)) { 1684 return; 1685 } 1686 1687 String[] splitPaths = split(thePathExpression); 1688 for (String nextPath : splitPaths) { 1689 nextPath = trim(nextPath); 1690 for (IBase nextObject : extractValues(nextPath, theResource)) { 1691 if (nextObject != null) { 1692 String typeName = toRootTypeName(nextObject); 1693 if (!myIgnoredForSearchDatatypes.contains(typeName)) { 1694 theExtractor.extract( 1695 theSetToPopulate, theSearchParameterDef, nextObject, nextPath, theWantLocalReferences); 1696 } 1697 } 1698 } 1699 } 1700 } 1701 1702 @Override 1703 public String toRootTypeName(IBase nextObject) { 1704 BaseRuntimeElementDefinition<?> elementDefinition = getContext().getElementDefinition(nextObject.getClass()); 1705 BaseRuntimeElementDefinition<?> rootParentDefinition = elementDefinition.getRootParentDefinition(); 1706 return rootParentDefinition.getName(); 1707 } 1708 1709 @Override 1710 public String toTypeName(IBase nextObject) { 1711 BaseRuntimeElementDefinition<?> elementDefinition = getContext().getElementDefinition(nextObject.getClass()); 1712 return elementDefinition.getName(); 1713 } 1714 1715 private void addUri_Uri( 1716 String theResourceType, 1717 Set<ResourceIndexedSearchParamUri> theParams, 1718 RuntimeSearchParam theSearchParam, 1719 IBase theValue) { 1720 IPrimitiveType<?> value = (IPrimitiveType<?>) theValue; 1721 String valueAsString = value.getValueAsString(); 1722 if (isNotBlank(valueAsString)) { 1723 ResourceIndexedSearchParamUri nextEntity = new ResourceIndexedSearchParamUri( 1724 myPartitionSettings, theResourceType, theSearchParam.getName(), valueAsString); 1725 theParams.add(nextEntity); 1726 } 1727 } 1728 1729 @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"}) 1730 private void createStringIndexIfNotBlank( 1731 String theResourceType, 1732 Set<? extends BaseResourceIndexedSearchParam> theParams, 1733 RuntimeSearchParam theSearchParam, 1734 String theValue) { 1735 String value = theValue; 1736 if (isNotBlank(value)) { 1737 if (value.length() > ResourceIndexedSearchParamString.MAX_LENGTH) { 1738 value = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); 1739 } 1740 1741 String searchParamName = theSearchParam.getName(); 1742 String valueNormalized = StringUtil.normalizeStringForSearchIndexing(value); 1743 String valueEncoded = theSearchParam.encode(valueNormalized); 1744 1745 if (valueEncoded.length() > ResourceIndexedSearchParamString.MAX_LENGTH) { 1746 valueEncoded = valueEncoded.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); 1747 } 1748 1749 ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString( 1750 myPartitionSettings, getStorageSettings(), theResourceType, searchParamName, valueEncoded, value); 1751 1752 Set params = theParams; 1753 params.add(nextEntity); 1754 } 1755 } 1756 1757 private void createTokenIndexIfNotBlankAndAdd( 1758 String theResourceType, 1759 Set<BaseResourceIndexedSearchParam> theParams, 1760 RuntimeSearchParam theSearchParam, 1761 String theSystem, 1762 String theValue) { 1763 ResourceIndexedSearchParamToken nextEntity = 1764 createTokenIndexIfNotBlank(theResourceType, theSystem, theValue, theSearchParam.getName()); 1765 if (nextEntity != null) { 1766 theParams.add(nextEntity); 1767 } 1768 } 1769 1770 @VisibleForTesting 1771 public void setPartitionSettings(PartitionSettings thePartitionSettings) { 1772 myPartitionSettings = thePartitionSettings; 1773 } 1774 1775 private ResourceIndexedSearchParamToken createTokenIndexIfNotBlank( 1776 String theResourceType, String theSystem, String theValue, String searchParamName) { 1777 ResourceIndexedSearchParamToken nextEntity = null; 1778 if (isNotBlank(theSystem) || isNotBlank(theValue)) { 1779 nextEntity = new ResourceIndexedSearchParamToken( 1780 myPartitionSettings, theResourceType, searchParamName, theSystem, theValue); 1781 } 1782 return nextEntity; 1783 } 1784 1785 @Override 1786 public String[] split(String thePaths) { 1787 if (shouldAttemptToSplitPath(thePaths)) { 1788 return splitOutOfParensOrs(thePaths); 1789 } else { 1790 return new String[] {thePaths}; 1791 } 1792 } 1793 1794 public boolean shouldAttemptToSplitPath(String thePath) { 1795 if (getContext().getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) { 1796 if (thePath.contains("|")) { 1797 return true; 1798 } 1799 } else { 1800 // DSTU 3 and below used "or" as well as "|" 1801 if (thePath.contains("|") || thePath.contains(" or ")) { 1802 return true; 1803 } 1804 } 1805 return false; 1806 } 1807 1808 /** 1809 * Iteratively splits a string on any ` or ` or | that is ** not** contained inside a set of parentheses. e.g. 1810 * <p> 1811 * "Patient.select(a or b)" --> ["Patient.select(a or b)"] 1812 * "Patient.select(a or b) or Patient.select(c or d )" --> ["Patient.select(a or b)", "Patient.select(c or d)"] 1813 * "Patient.select(a|b) or Patient.select(c or d )" --> ["Patient.select(a|b)", "Patient.select(c or d)"] 1814 * "Patient.select(b) | Patient.select(c)" --> ["Patient.select(b)", "Patient.select(c)"] 1815 * 1816 * @param thePaths The string to split 1817 * @return The split string 1818 */ 1819 private String[] splitOutOfParensOrs(String thePaths) { 1820 List<String> topLevelOrExpressions = splitOutOfParensToken(thePaths, " or "); 1821 List<String> retVal = topLevelOrExpressions.stream() 1822 .flatMap(s -> splitOutOfParensToken(s, " |").stream()) 1823 .collect(Collectors.toList()); 1824 return retVal.toArray(new String[retVal.size()]); 1825 } 1826 1827 private List<String> splitOutOfParensToken(String thePath, String theToken) { 1828 int tokenLength = theToken.length(); 1829 int index = thePath.indexOf(theToken); 1830 int rightIndex = 0; 1831 List<String> retVal = new ArrayList<>(); 1832 while (index > -1) { 1833 String left = thePath.substring(rightIndex, index); 1834 if (allParensHaveBeenClosed(left)) { 1835 retVal.add(left); 1836 rightIndex = index + tokenLength; 1837 } 1838 index = thePath.indexOf(theToken, index + tokenLength); 1839 } 1840 retVal.add(thePath.substring(rightIndex)); 1841 return retVal; 1842 } 1843 1844 private boolean allParensHaveBeenClosed(String thePaths) { 1845 int open = StringUtils.countMatches(thePaths, "("); 1846 int close = StringUtils.countMatches(thePaths, ")"); 1847 return open == close; 1848 } 1849 1850 private BigDecimal normalizeQuantityContainingTimeUnitsIntoDaysForNumberParam( 1851 String theSystem, String theCode, BigDecimal theValue) { 1852 if (SearchParamConstants.UCUM_NS.equals(theSystem)) { 1853 if (isNotBlank(theCode)) { 1854 Unit<? extends Quantity> unit = Unit.valueOf(theCode); 1855 javax.measure.converter.UnitConverter dayConverter = unit.getConverterTo(NonSI.DAY); 1856 double dayValue = dayConverter.convert(theValue.doubleValue()); 1857 theValue = new BigDecimal(dayValue); 1858 } 1859 } 1860 return theValue; 1861 } 1862 1863 @PostConstruct 1864 public void start() { 1865 myIgnoredForSearchDatatypes = new HashSet<>(); 1866 addIgnoredType(getContext(), "Annotation", myIgnoredForSearchDatatypes); 1867 addIgnoredType(getContext(), "Attachment", myIgnoredForSearchDatatypes); 1868 addIgnoredType(getContext(), "Count", myIgnoredForSearchDatatypes); 1869 addIgnoredType(getContext(), "Distance", myIgnoredForSearchDatatypes); 1870 addIgnoredType(getContext(), "Ratio", myIgnoredForSearchDatatypes); 1871 addIgnoredType(getContext(), "SampledData", myIgnoredForSearchDatatypes); 1872 addIgnoredType(getContext(), "Signature", myIgnoredForSearchDatatypes); 1873 1874 /* 1875 * This is building up an internal map of all the various field accessors we'll need in order to work 1876 * with the model. This is kind of ugly, but we want to be as efficient as possible since 1877 * search param extraction happens a whole heck of a lot at runtime.. 1878 */ 1879 1880 BaseRuntimeElementCompositeDefinition<?> quantityDefinition = 1881 (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Quantity"); 1882 myQuantityValueValueChild = quantityDefinition.getChildByName("value"); 1883 myQuantitySystemValueChild = quantityDefinition.getChildByName("system"); 1884 myQuantityCodeValueChild = quantityDefinition.getChildByName("code"); 1885 1886 BaseRuntimeElementCompositeDefinition<?> moneyDefinition = 1887 (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Money"); 1888 myMoneyValueChild = moneyDefinition.getChildByName("value"); 1889 myMoneyCurrencyChild = moneyDefinition.getChildByName("currency"); 1890 1891 BaseRuntimeElementCompositeDefinition<?> locationDefinition = 1892 getContext().getResourceDefinition("Location"); 1893 BaseRuntimeChildDefinition locationPositionValueChild = locationDefinition.getChildByName("position"); 1894 myLocationPositionDefinition = 1895 (BaseRuntimeElementCompositeDefinition<?>) locationPositionValueChild.getChildByName("position"); 1896 1897 BaseRuntimeElementCompositeDefinition<?> rangeDefinition = 1898 (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Range"); 1899 myRangeLowValueChild = rangeDefinition.getChildByName("low"); 1900 myRangeHighValueChild = rangeDefinition.getChildByName("high"); 1901 1902 BaseRuntimeElementCompositeDefinition<?> addressDefinition = 1903 (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Address"); 1904 myAddressLineValueChild = addressDefinition.getChildByName("line"); 1905 myAddressCityValueChild = addressDefinition.getChildByName("city"); 1906 myAddressDistrictValueChild = addressDefinition.getChildByName("district"); 1907 myAddressStateValueChild = addressDefinition.getChildByName("state"); 1908 myAddressCountryValueChild = addressDefinition.getChildByName("country"); 1909 myAddressPostalCodeValueChild = addressDefinition.getChildByName("postalCode"); 1910 1911 BaseRuntimeElementCompositeDefinition<?> periodDefinition = 1912 (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Period"); 1913 myPeriodStartValueChild = periodDefinition.getChildByName("start"); 1914 myPeriodEndValueChild = periodDefinition.getChildByName("end"); 1915 1916 BaseRuntimeElementCompositeDefinition<?> timingDefinition = 1917 (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Timing"); 1918 myTimingEventValueChild = timingDefinition.getChildByName("event"); 1919 myTimingRepeatValueChild = timingDefinition.getChildByName("repeat"); 1920 BaseRuntimeElementCompositeDefinition<?> timingRepeatDefinition = 1921 (BaseRuntimeElementCompositeDefinition<?>) myTimingRepeatValueChild.getChildByName("repeat"); 1922 myTimingRepeatBoundsValueChild = timingRepeatDefinition.getChildByName("bounds[x]"); 1923 1924 BaseRuntimeElementCompositeDefinition<?> durationDefinition = 1925 (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Duration"); 1926 myDurationSystemValueChild = durationDefinition.getChildByName("system"); 1927 myDurationCodeValueChild = durationDefinition.getChildByName("code"); 1928 myDurationValueValueChild = durationDefinition.getChildByName("value"); 1929 1930 BaseRuntimeElementCompositeDefinition<?> humanNameDefinition = 1931 (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("HumanName"); 1932 myHumanNameFamilyValueChild = humanNameDefinition.getChildByName("family"); 1933 myHumanNameGivenValueChild = humanNameDefinition.getChildByName("given"); 1934 myHumanNameTextValueChild = humanNameDefinition.getChildByName("text"); 1935 myHumanNamePrefixValueChild = humanNameDefinition.getChildByName("prefix"); 1936 myHumanNameSuffixValueChild = humanNameDefinition.getChildByName("suffix"); 1937 1938 BaseRuntimeElementCompositeDefinition<?> contactPointDefinition = 1939 (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("ContactPoint"); 1940 myContactPointValueValueChild = contactPointDefinition.getChildByName("value"); 1941 myContactPointSystemValueChild = contactPointDefinition.getChildByName("system"); 1942 1943 BaseRuntimeElementCompositeDefinition<?> identifierDefinition = 1944 (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Identifier"); 1945 myIdentifierSystemValueChild = identifierDefinition.getChildByName("system"); 1946 myIdentifierValueValueChild = identifierDefinition.getChildByName("value"); 1947 myIdentifierTypeValueChild = identifierDefinition.getChildByName("type"); 1948 BaseRuntimeElementCompositeDefinition<?> identifierTypeDefinition = 1949 (BaseRuntimeElementCompositeDefinition<?>) myIdentifierTypeValueChild.getChildByName("type"); 1950 myIdentifierTypeTextValueChild = identifierTypeDefinition.getChildByName("text"); 1951 1952 BaseRuntimeElementCompositeDefinition<?> codeableConceptDefinition = 1953 (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("CodeableConcept"); 1954 myCodeableConceptCodingValueChild = codeableConceptDefinition.getChildByName("coding"); 1955 myCodeableConceptTextValueChild = codeableConceptDefinition.getChildByName("text"); 1956 1957 BaseRuntimeElementCompositeDefinition<?> codingDefinition = 1958 (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Coding"); 1959 myCodingSystemValueChild = codingDefinition.getChildByName("system"); 1960 myCodingCodeValueChild = codingDefinition.getChildByName("code"); 1961 myCodingDisplayValueChild = codingDefinition.getChildByName("display"); 1962 1963 BaseRuntimeElementCompositeDefinition<?> patientDefinition = 1964 getContext().getResourceDefinition("Patient"); 1965 BaseRuntimeChildDefinition patientCommunicationValueChild = patientDefinition.getChildByName("communication"); 1966 BaseRuntimeElementCompositeDefinition<?> patientCommunicationDefinition = 1967 (BaseRuntimeElementCompositeDefinition<?>) 1968 patientCommunicationValueChild.getChildByName("communication"); 1969 myPatientCommunicationLanguageValueChild = patientCommunicationDefinition.getChildByName("language"); 1970 1971 // DSTU3+ 1972 BaseRuntimeElementCompositeDefinition<?> codeSystemDefinition; 1973 if (getContext().getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { 1974 codeSystemDefinition = getContext().getResourceDefinition("CodeSystem"); 1975 assert codeSystemDefinition != null; 1976 myCodeSystemUrlValueChild = codeSystemDefinition.getChildByName("url"); 1977 1978 BaseRuntimeElementCompositeDefinition<?> capabilityStatementDefinition = 1979 getContext().getResourceDefinition("CapabilityStatement"); 1980 BaseRuntimeChildDefinition capabilityStatementRestChild = 1981 capabilityStatementDefinition.getChildByName("rest"); 1982 BaseRuntimeElementCompositeDefinition<?> capabilityStatementRestDefinition = 1983 (BaseRuntimeElementCompositeDefinition<?>) capabilityStatementRestChild.getChildByName("rest"); 1984 BaseRuntimeChildDefinition capabilityStatementRestSecurityValueChild = 1985 capabilityStatementRestDefinition.getChildByName("security"); 1986 BaseRuntimeElementCompositeDefinition<?> capabilityStatementRestSecurityDefinition = 1987 (BaseRuntimeElementCompositeDefinition<?>) 1988 capabilityStatementRestSecurityValueChild.getChildByName("security"); 1989 myCapabilityStatementRestSecurityServiceValueChild = 1990 capabilityStatementRestSecurityDefinition.getChildByName("service"); 1991 } 1992 1993 // R4B+ 1994 if (getContext().getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4B)) { 1995 1996 BaseRuntimeElementCompositeDefinition<?> codeableReferenceDef = 1997 (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("CodeableReference"); 1998 myCodeableReferenceConcept = codeableReferenceDef.getChildByName("concept"); 1999 myCodeableReferenceReference = codeableReferenceDef.getChildByName("reference"); 2000 } 2001 } 2002 2003 @FunctionalInterface 2004 public interface IValueExtractor { 2005 2006 List<? extends IBase> get() throws FHIRException; 2007 } 2008 2009 @FunctionalInterface 2010 private interface IExtractor<T> { 2011 2012 void extract( 2013 SearchParamSet<T> theParams, 2014 RuntimeSearchParam theSearchParam, 2015 IBase theValue, 2016 String thePath, 2017 boolean theWantLocalReferences); 2018 } 2019 2020 /** 2021 * Note that this should only be called for R4+ servers. Prior to 2022 * R4 the paths could be separated by the word "or" or by a "|" 2023 * character, so we used a slower splitting mechanism. 2024 */ 2025 @Nonnull 2026 public static String[] splitPathsR4(@Nonnull String thePaths) { 2027 StringTokenizer tok = new StringTokenizer(thePaths, " |"); 2028 tok.setTrimmerMatcher(new StringTrimmingTrimmerMatcher()); 2029 return tok.getTokenArray(); 2030 } 2031 2032 public static boolean tokenTextIndexingEnabledForSearchParam( 2033 StorageSettings theStorageSettings, RuntimeSearchParam theSearchParam) { 2034 Optional<Boolean> noSuppressForSearchParam = 2035 theSearchParam.getExtensions(HapiExtensions.EXT_SEARCHPARAM_TOKEN_SUPPRESS_TEXT_INDEXING).stream() 2036 .map(IBaseExtension::getValue) 2037 .map(val -> (IPrimitiveType<?>) val) 2038 .map(IPrimitiveType::getValueAsString) 2039 .map(Boolean::parseBoolean) 2040 .findFirst(); 2041 2042 // if the SP doesn't care, use the system default. 2043 if (!noSuppressForSearchParam.isPresent()) { 2044 return !theStorageSettings.isSuppressStringIndexingInTokens(); 2045 // If the SP does care, use its value. 2046 } else { 2047 boolean suppressForSearchParam = noSuppressForSearchParam.get(); 2048 ourLog.trace("Text indexing for SearchParameter {}: {}", theSearchParam.getName(), suppressForSearchParam); 2049 return !suppressForSearchParam; 2050 } 2051 } 2052 2053 private static void addIgnoredType(FhirContext theCtx, String theType, Set<String> theIgnoredTypes) { 2054 BaseRuntimeElementDefinition<?> elementDefinition = theCtx.getElementDefinition(theType); 2055 if (elementDefinition != null) { 2056 theIgnoredTypes.add(elementDefinition.getName()); 2057 } 2058 } 2059 2060 protected static String extractValueAsString(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) { 2061 return theChildDefinition 2062 .getAccessor() 2063 .<IPrimitiveType<?>>getFirstValueOrNull(theElement) 2064 .map(IPrimitiveType::getValueAsString) 2065 .orElse(null); 2066 } 2067 2068 protected static Date extractValueAsDate(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) { 2069 return theChildDefinition 2070 .getAccessor() 2071 .<IPrimitiveType<Date>>getFirstValueOrNull(theElement) 2072 .map(IPrimitiveType::getValue) 2073 .orElse(null); 2074 } 2075 2076 protected static BigDecimal extractValueAsBigDecimal( 2077 BaseRuntimeChildDefinition theChildDefinition, IBase theElement) { 2078 return theChildDefinition 2079 .getAccessor() 2080 .<IPrimitiveType<BigDecimal>>getFirstValueOrNull(theElement) 2081 .map(IPrimitiveType::getValue) 2082 .orElse(null); 2083 } 2084 2085 @SuppressWarnings("unchecked") 2086 protected static List<IPrimitiveType<Date>> extractValuesAsFhirDates( 2087 BaseRuntimeChildDefinition theChildDefinition, IBase theElement) { 2088 return (List) theChildDefinition.getAccessor().getValues(theElement); 2089 } 2090 2091 protected static List<String> extractValuesAsStrings( 2092 BaseRuntimeChildDefinition theChildDefinition, IBase theValue) { 2093 return theChildDefinition.getAccessor().getValues(theValue).stream() 2094 .map(t -> (IPrimitiveType) t) 2095 .map(IPrimitiveType::getValueAsString) 2096 .filter(StringUtils::isNotBlank) 2097 .collect(Collectors.toList()); 2098 } 2099 2100 protected static <T extends Enum<?>> String extractSystem(IBaseEnumeration<T> theBoundCode) { 2101 if (theBoundCode.getValue() != null) { 2102 return theBoundCode.getEnumFactory().toSystem(theBoundCode.getValue()); 2103 } 2104 return null; 2105 } 2106 2107 private class ResourceLinkExtractor implements IExtractor<PathAndRef> { 2108 2109 private PathAndRef myPathAndRef = null; 2110 2111 @Override 2112 public void extract( 2113 SearchParamSet<PathAndRef> theParams, 2114 RuntimeSearchParam theSearchParam, 2115 IBase theValue, 2116 String thePath, 2117 boolean theWantLocalReferences) { 2118 if (theValue instanceof IBaseResource) { 2119 myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, (IBaseResource) theValue); 2120 theParams.add(myPathAndRef); 2121 return; 2122 } 2123 2124 String nextType = toRootTypeName(theValue); 2125 switch (nextType) { 2126 case "uri": 2127 case "canonical": 2128 String typeName = toTypeName(theValue); 2129 IPrimitiveType<?> valuePrimitive = (IPrimitiveType<?>) theValue; 2130 IBaseReference fakeReference = (IBaseReference) 2131 myContext.getElementDefinition("Reference").newInstance(); 2132 fakeReference.setReference(valuePrimitive.getValueAsString()); 2133 2134 // Canonical has a root type of "uri" 2135 if ("canonical".equals(typeName)) { 2136 2137 /* 2138 * See #1583 2139 * Technically canonical fields should not allow local references (e.g. 2140 * Questionnaire/123) but it seems reasonable for us to interpret a canonical 2141 * containing a local reference for what it is, and allow people to search 2142 * based on that. 2143 */ 2144 IIdType parsed = fakeReference.getReferenceElement(); 2145 if (parsed.hasIdPart() && parsed.hasResourceType() && !parsed.isAbsolute()) { 2146 myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, fakeReference, false); 2147 theParams.add(myPathAndRef); 2148 break; 2149 } 2150 2151 if (parsed.isAbsolute()) { 2152 myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, fakeReference, true); 2153 theParams.add(myPathAndRef); 2154 break; 2155 } 2156 } 2157 2158 theParams.addWarning("Ignoring canonical reference (indexing canonical is not yet supported)"); 2159 break; 2160 case "reference": 2161 case "Reference": 2162 IBaseReference valueRef = (IBaseReference) theValue; 2163 extractResourceLinkFromReference( 2164 theParams, theSearchParam, thePath, theWantLocalReferences, valueRef); 2165 break; 2166 case "CodeableReference": 2167 Optional<IBase> referenceOpt = 2168 myCodeableReferenceReference.getAccessor().getFirstValueOrNull(theValue); 2169 if (referenceOpt.isPresent()) { 2170 IBaseReference value = (IBaseReference) referenceOpt.get(); 2171 extractResourceLinkFromReference( 2172 theParams, theSearchParam, thePath, theWantLocalReferences, value); 2173 } 2174 break; 2175 default: 2176 addUnexpectedDatatypeWarning(theParams, theSearchParam, theValue, thePath); 2177 break; 2178 } 2179 } 2180 2181 private void extractResourceLinkFromReference( 2182 SearchParamSet<PathAndRef> theParams, 2183 RuntimeSearchParam theSearchParam, 2184 String thePath, 2185 boolean theWantLocalReferences, 2186 IBaseReference valueRef) { 2187 IIdType nextId = valueRef.getReferenceElement(); 2188 if (nextId.isEmpty() && valueRef.getResource() != null) { 2189 nextId = valueRef.getResource().getIdElement(); 2190 } 2191 2192 if (nextId == null || nextId.isEmpty()) { 2193 // Ignore placeholder references that are blank 2194 } else if (!theWantLocalReferences && nextId.getValue().startsWith("#")) { 2195 // Ignore local refs unless we specifically want them 2196 } else { 2197 myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, valueRef, false); 2198 theParams.add(myPathAndRef); 2199 } 2200 } 2201 2202 public PathAndRef get(IBase theValue, String thePath) { 2203 extract( 2204 new SearchParamSet<>(), 2205 new RuntimeSearchParam(null, null, "Reference", null, null, null, null, null, null, null), 2206 theValue, 2207 thePath, 2208 false); 2209 return myPathAndRef; 2210 } 2211 } 2212 2213 private class DateExtractor implements IExtractor<ResourceIndexedSearchParamDate> { 2214 2215 final String myResourceType; 2216 ResourceIndexedSearchParamDate myIndexedSearchParamDate = null; 2217 2218 public DateExtractor(IBaseResource theResource) { 2219 this(toRootTypeName(theResource)); 2220 } 2221 2222 public DateExtractor(String theResourceType) { 2223 myResourceType = theResourceType; 2224 } 2225 2226 @Override 2227 public void extract( 2228 SearchParamSet theParams, 2229 RuntimeSearchParam theSearchParam, 2230 IBase theValue, 2231 String thePath, 2232 boolean theWantLocalReferences) { 2233 String nextType = toRootTypeName(theValue); 2234 switch (nextType) { 2235 case "date": 2236 case "dateTime": 2237 case "instant": 2238 addDateTimeTypes(myResourceType, theParams, theSearchParam, theValue); 2239 break; 2240 case "Period": 2241 addDate_Period(myResourceType, theParams, theSearchParam, theValue); 2242 break; 2243 case "Timing": 2244 addDate_Timing(myResourceType, theParams, theSearchParam, theValue); 2245 break; 2246 case "string": 2247 // CarePlan.activitydate can be a string - ignored for now 2248 break; 2249 case "Quantity": 2250 // Condition.onset[x] can have a Quantity - Ignored for now 2251 break; 2252 case "Range": 2253 // Condition.onset[x] can have a Range - Ignored for now 2254 break; 2255 default: 2256 addUnexpectedDatatypeWarning(theParams, theSearchParam, theValue, thePath); 2257 break; 2258 } 2259 } 2260 2261 private void addDate_Period( 2262 String theResourceType, 2263 Set<ResourceIndexedSearchParamDate> theParams, 2264 RuntimeSearchParam theSearchParam, 2265 IBase theValue) { 2266 Date start = extractValueAsDate(myPeriodStartValueChild, theValue); 2267 String startAsString = extractValueAsString(myPeriodStartValueChild, theValue); 2268 Date end = extractValueAsDate(myPeriodEndValueChild, theValue); 2269 String endAsString = extractValueAsString(myPeriodEndValueChild, theValue); 2270 2271 if (start != null || end != null) { 2272 2273 if (start == null) { 2274 start = myStorageSettings.getPeriodIndexStartOfTime().getValue(); 2275 startAsString = 2276 myStorageSettings.getPeriodIndexStartOfTime().getValueAsString(); 2277 } 2278 if (end == null) { 2279 end = myStorageSettings.getPeriodIndexEndOfTime().getValue(); 2280 endAsString = myStorageSettings.getPeriodIndexEndOfTime().getValueAsString(); 2281 } 2282 2283 myIndexedSearchParamDate = new ResourceIndexedSearchParamDate( 2284 myPartitionSettings, 2285 theResourceType, 2286 theSearchParam.getName(), 2287 start, 2288 startAsString, 2289 end, 2290 endAsString, 2291 startAsString); 2292 theParams.add(myIndexedSearchParamDate); 2293 } 2294 } 2295 2296 private void addDate_Timing( 2297 String theResourceType, 2298 Set<ResourceIndexedSearchParamDate> theParams, 2299 RuntimeSearchParam theSearchParam, 2300 IBase theValue) { 2301 List<IPrimitiveType<Date>> values = extractValuesAsFhirDates(myTimingEventValueChild, theValue); 2302 2303 TreeSet<Date> dates = new TreeSet<>(); 2304 String firstValue = null; 2305 String finalValue = null; 2306 for (IPrimitiveType<Date> nextEvent : values) { 2307 if (nextEvent.getValue() != null) { 2308 dates.add(nextEvent.getValue()); 2309 if (firstValue == null) { 2310 firstValue = nextEvent.getValueAsString(); 2311 } 2312 finalValue = nextEvent.getValueAsString(); 2313 } 2314 } 2315 2316 Optional<IBase> repeat = myTimingRepeatValueChild.getAccessor().getFirstValueOrNull(theValue); 2317 if (repeat.isPresent()) { 2318 Optional<IBase> bounds = 2319 myTimingRepeatBoundsValueChild.getAccessor().getFirstValueOrNull(repeat.get()); 2320 if (bounds.isPresent()) { 2321 String boundsType = toRootTypeName(bounds.get()); 2322 if ("Period".equals(boundsType)) { 2323 Date start = extractValueAsDate(myPeriodStartValueChild, bounds.get()); 2324 Date end = extractValueAsDate(myPeriodEndValueChild, bounds.get()); 2325 if (start != null) { 2326 dates.add(start); 2327 } 2328 if (end != null) { 2329 dates.add(end); 2330 } 2331 } 2332 } 2333 } 2334 2335 if (!dates.isEmpty()) { 2336 myIndexedSearchParamDate = new ResourceIndexedSearchParamDate( 2337 myPartitionSettings, 2338 theResourceType, 2339 theSearchParam.getName(), 2340 dates.first(), 2341 firstValue, 2342 dates.last(), 2343 finalValue, 2344 firstValue); 2345 theParams.add(myIndexedSearchParamDate); 2346 } 2347 } 2348 2349 @SuppressWarnings("unchecked") 2350 private void addDateTimeTypes( 2351 String theResourceType, 2352 Set<ResourceIndexedSearchParamDate> theParams, 2353 RuntimeSearchParam theSearchParam, 2354 IBase theValue) { 2355 IPrimitiveType<Date> nextBaseDateTime = (IPrimitiveType<Date>) theValue; 2356 if (nextBaseDateTime.getValue() != null) { 2357 myIndexedSearchParamDate = new ResourceIndexedSearchParamDate( 2358 myPartitionSettings, 2359 theResourceType, 2360 theSearchParam.getName(), 2361 nextBaseDateTime.getValue(), 2362 nextBaseDateTime.getValueAsString(), 2363 nextBaseDateTime.getValue(), 2364 nextBaseDateTime.getValueAsString(), 2365 nextBaseDateTime.getValueAsString()); 2366 ourLog.trace("DateExtractor - extracted {} for {}", nextBaseDateTime, theSearchParam.getName()); 2367 theParams.add(myIndexedSearchParamDate); 2368 } 2369 } 2370 2371 public ResourceIndexedSearchParamDate get(IBase theValue, String thePath, boolean theWantLocalReferences) { 2372 extract( 2373 new SearchParamSet<>(), 2374 new RuntimeSearchParam(null, null, "date", null, null, null, null, null, null, null), 2375 theValue, 2376 thePath, 2377 theWantLocalReferences); 2378 return myIndexedSearchParamDate; 2379 } 2380 } 2381 2382 private class TokenExtractor implements IExtractor<BaseResourceIndexedSearchParam> { 2383 private final String myResourceTypeName; 2384 private final String myUseSystem; 2385 2386 public TokenExtractor(String theResourceTypeName, String theUseSystem) { 2387 myResourceTypeName = theResourceTypeName; 2388 myUseSystem = theUseSystem; 2389 } 2390 2391 @Override 2392 public void extract( 2393 SearchParamSet<BaseResourceIndexedSearchParam> params, 2394 RuntimeSearchParam searchParam, 2395 IBase value, 2396 String path, 2397 boolean theWantLocalReferences) { 2398 2399 // DSTU3+ 2400 if (value instanceof IBaseEnumeration<?>) { 2401 IBaseEnumeration<?> obj = (IBaseEnumeration<?>) value; 2402 String system = extractSystem(obj); 2403 String code = obj.getValueAsString(); 2404 BaseSearchParamExtractor.this.createTokenIndexIfNotBlankAndAdd( 2405 myResourceTypeName, params, searchParam, system, code); 2406 return; 2407 } 2408 2409 // DSTU2 only 2410 if (value instanceof BoundCodeDt) { 2411 BoundCodeDt boundCode = (BoundCodeDt) value; 2412 Enum valueAsEnum = boundCode.getValueAsEnum(); 2413 String system = null; 2414 if (valueAsEnum != null) { 2415 //noinspection unchecked 2416 system = boundCode.getBinder().toSystemString(valueAsEnum); 2417 } 2418 String code = boundCode.getValueAsString(); 2419 BaseSearchParamExtractor.this.createTokenIndexIfNotBlankAndAdd( 2420 myResourceTypeName, params, searchParam, system, code); 2421 return; 2422 } 2423 2424 if (value instanceof IPrimitiveType) { 2425 IPrimitiveType<?> nextValue = (IPrimitiveType<?>) value; 2426 String systemAsString = null; 2427 String valueAsString = nextValue.getValueAsString(); 2428 if ("CodeSystem.concept.code".equals(path)) { 2429 systemAsString = myUseSystem; 2430 } else if ("ValueSet.codeSystem.concept.code".equals(path)) { 2431 systemAsString = myUseSystem; 2432 } 2433 2434 if (value instanceof IIdType) { 2435 valueAsString = ((IIdType) value).getIdPart(); 2436 } 2437 2438 BaseSearchParamExtractor.this.createTokenIndexIfNotBlankAndAdd( 2439 myResourceTypeName, params, searchParam, systemAsString, valueAsString); 2440 return; 2441 } 2442 2443 switch (path) { 2444 case "Patient.communication": 2445 BaseSearchParamExtractor.this.addToken_PatientCommunication( 2446 myResourceTypeName, params, searchParam, value); 2447 return; 2448 case "Consent.source": 2449 // Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that 2450 return; 2451 case "Location.position": 2452 BaseSearchParamExtractor.this.addCoords_Position(myResourceTypeName, params, searchParam, value); 2453 return; 2454 case "StructureDefinition.context": 2455 // TODO: implement this 2456 ourLog.warn("StructureDefinition context indexing not currently supported"); 2457 return; 2458 case "CapabilityStatement.rest.security": 2459 BaseSearchParamExtractor.this.addToken_CapabilityStatementRestSecurity( 2460 myResourceTypeName, params, searchParam, value); 2461 return; 2462 } 2463 2464 String nextType = BaseSearchParamExtractor.this.toRootTypeName(value); 2465 switch (nextType) { 2466 case "Identifier": 2467 addToken_Identifier(myResourceTypeName, params, searchParam, value); 2468 break; 2469 case "CodeableConcept": 2470 addToken_CodeableConcept(myResourceTypeName, params, searchParam, value); 2471 break; 2472 case "CodeableReference": 2473 addToken_CodeableReference(myResourceTypeName, params, searchParam, value); 2474 break; 2475 case "Coding": 2476 addToken_Coding(myResourceTypeName, params, searchParam, value); 2477 break; 2478 case "ContactPoint": 2479 addToken_ContactPoint(myResourceTypeName, params, searchParam, value); 2480 break; 2481 case "Range": 2482 // Group.characteristic.value[x] can have a Range - Ignored for now 2483 break; 2484 case "Quantity": 2485 // Group.characteristic.value[x] can have a Quantity - Ignored for now 2486 break; 2487 default: 2488 addUnexpectedDatatypeWarning(params, searchParam, value, path); 2489 break; 2490 } 2491 } 2492 } 2493 2494 /** 2495 * Extractor that delegates to two other extractors. 2496 * 2497 * @param <T> the type (currently only used for Numeric) 2498 */ 2499 private static class MultiplexExtractor<T> implements IExtractor<T> { 2500 2501 private final IExtractor<T> myExtractor0; 2502 private final IExtractor<T> myExtractor1; 2503 2504 private MultiplexExtractor(IExtractor<T> theExtractor0, IExtractor<T> theExtractor1) { 2505 myExtractor0 = theExtractor0; 2506 myExtractor1 = theExtractor1; 2507 } 2508 2509 @Override 2510 public void extract( 2511 SearchParamSet<T> theParams, 2512 RuntimeSearchParam theSearchParam, 2513 IBase theValue, 2514 String thePath, 2515 boolean theWantLocalReferences) { 2516 myExtractor0.extract(theParams, theSearchParam, theValue, thePath, theWantLocalReferences); 2517 myExtractor1.extract(theParams, theSearchParam, theValue, thePath, theWantLocalReferences); 2518 } 2519 } 2520}