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