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