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