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