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