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