
001package ca.uhn.fhir.jpa.searchparam.extractor; 002 003/* 004 * #%L 005 * HAPI FHIR Search Parameters 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.context.BaseRuntimeChildDefinition; 024import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; 025import ca.uhn.fhir.context.BaseRuntimeElementDefinition; 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.ModelConfig; 034import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel; 035import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords; 036import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; 037import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber; 038import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; 039import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized; 040import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; 041import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; 042import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri; 043import ca.uhn.fhir.jpa.model.util.UcumServiceUtil; 044import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; 045import ca.uhn.fhir.model.primitive.BoundCodeDt; 046import ca.uhn.fhir.rest.api.Constants; 047import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; 048import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 049import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; 050import ca.uhn.fhir.util.FhirTerser; 051import ca.uhn.fhir.util.HapiExtensions; 052import ca.uhn.fhir.util.StringUtil; 053import com.google.common.annotations.VisibleForTesting; 054import com.google.common.collect.Sets; 055import org.apache.commons.lang3.ObjectUtils; 056import org.apache.commons.lang3.StringUtils; 057import org.apache.commons.lang3.Validate; 058import org.apache.commons.text.StringTokenizer; 059import org.fhir.ucum.Pair; 060import org.hl7.fhir.exceptions.FHIRException; 061import org.hl7.fhir.instance.model.api.IBase; 062import org.hl7.fhir.instance.model.api.IBaseEnumeration; 063import org.hl7.fhir.instance.model.api.IBaseExtension; 064import org.hl7.fhir.instance.model.api.IBaseReference; 065import org.hl7.fhir.instance.model.api.IBaseResource; 066import org.hl7.fhir.instance.model.api.IIdType; 067import org.hl7.fhir.instance.model.api.IPrimitiveType; 068import org.springframework.beans.factory.annotation.Autowired; 069import org.springframework.context.ApplicationContext; 070 071import javax.annotation.Nonnull; 072import javax.annotation.PostConstruct; 073import javax.measure.quantity.Quantity; 074import javax.measure.unit.NonSI; 075import javax.measure.unit.Unit; 076import java.math.BigDecimal; 077import java.util.ArrayList; 078import java.util.Arrays; 079import java.util.Collection; 080import java.util.Collections; 081import java.util.Date; 082import java.util.HashSet; 083import java.util.List; 084import java.util.Objects; 085import java.util.Optional; 086import java.util.Set; 087import java.util.TreeSet; 088import java.util.stream.Collectors; 089 090import static org.apache.commons.lang3.StringUtils.isBlank; 091import static org.apache.commons.lang3.StringUtils.isNotBlank; 092import static org.apache.commons.lang3.StringUtils.trim; 093 094public abstract class BaseSearchParamExtractor implements ISearchParamExtractor { 095 096 public static final Set<String> COORDS_INDEX_PATHS; 097 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseSearchParamExtractor.class); 098 099 static { 100 Set<String> coordsIndexPaths = Sets.newHashSet("Location.position"); 101 COORDS_INDEX_PATHS = Collections.unmodifiableSet(coordsIndexPaths); 102 } 103 104 @Autowired 105 protected ApplicationContext myApplicationContext; 106 @Autowired 107 private FhirContext myContext; 108 @Autowired 109 private ISearchParamRegistry mySearchParamRegistry; 110 @Autowired 111 private ModelConfig myModelConfig; 112 @Autowired 113 private PartitionSettings myPartitionSettings; 114 private Set<String> myIgnoredForSearchDatatypes; 115 private BaseRuntimeChildDefinition myQuantityValueValueChild; 116 private BaseRuntimeChildDefinition myQuantitySystemValueChild; 117 private BaseRuntimeChildDefinition myQuantityCodeValueChild; 118 private BaseRuntimeChildDefinition myMoneyValueChild; 119 private BaseRuntimeChildDefinition myMoneyCurrencyChild; 120 private BaseRuntimeElementCompositeDefinition<?> myLocationPositionDefinition; 121 private BaseRuntimeChildDefinition myCodeSystemUrlValueChild; 122 private BaseRuntimeChildDefinition myRangeLowValueChild; 123 private BaseRuntimeChildDefinition myRangeHighValueChild; 124 private BaseRuntimeChildDefinition myAddressLineValueChild; 125 private BaseRuntimeChildDefinition myAddressCityValueChild; 126 private BaseRuntimeChildDefinition myAddressStateValueChild; 127 private BaseRuntimeChildDefinition myAddressCountryValueChild; 128 private BaseRuntimeChildDefinition myAddressPostalCodeValueChild; 129 private BaseRuntimeChildDefinition myCapabilityStatementRestSecurityServiceValueChild; 130 private BaseRuntimeChildDefinition myPeriodStartValueChild; 131 private BaseRuntimeChildDefinition myPeriodEndValueChild; 132 private BaseRuntimeChildDefinition myTimingEventValueChild; 133 private BaseRuntimeChildDefinition myTimingRepeatValueChild; 134 private BaseRuntimeChildDefinition myTimingRepeatBoundsValueChild; 135 private BaseRuntimeChildDefinition myDurationSystemValueChild; 136 private BaseRuntimeChildDefinition myDurationCodeValueChild; 137 private BaseRuntimeChildDefinition myDurationValueValueChild; 138 private BaseRuntimeChildDefinition myHumanNameFamilyValueChild; 139 private BaseRuntimeChildDefinition myHumanNameGivenValueChild; 140 private BaseRuntimeChildDefinition myHumanNameTextValueChild; 141 private BaseRuntimeChildDefinition myHumanNamePrefixValueChild; 142 private BaseRuntimeChildDefinition myHumanNameSuffixValueChild; 143 private BaseRuntimeChildDefinition myContactPointValueValueChild; 144 private BaseRuntimeChildDefinition myIdentifierSystemValueChild; 145 private BaseRuntimeChildDefinition myIdentifierValueValueChild; 146 private BaseRuntimeChildDefinition myIdentifierTypeValueChild; 147 private BaseRuntimeChildDefinition myIdentifierTypeTextValueChild; 148 private BaseRuntimeChildDefinition myCodeableConceptCodingValueChild; 149 private BaseRuntimeChildDefinition myCodeableConceptTextValueChild; 150 private BaseRuntimeChildDefinition myCodingSystemValueChild; 151 private BaseRuntimeChildDefinition myCodingCodeValueChild; 152 private BaseRuntimeChildDefinition myCodingDisplayValueChild; 153 private BaseRuntimeChildDefinition myContactPointSystemValueChild; 154 private BaseRuntimeChildDefinition myPatientCommunicationLanguageValueChild; 155 156 /** 157 * Constructor 158 */ 159 BaseSearchParamExtractor() { 160 super(); 161 } 162 163 /** 164 * UNIT TEST constructor 165 */ 166 BaseSearchParamExtractor(ModelConfig theModelConfig, PartitionSettings thePartitionSettings, FhirContext theCtx, ISearchParamRegistry theSearchParamRegistry) { 167 Validate.notNull(theModelConfig); 168 Validate.notNull(theCtx); 169 Validate.notNull(theSearchParamRegistry); 170 171 myModelConfig = theModelConfig; 172 myContext = theCtx; 173 mySearchParamRegistry = theSearchParamRegistry; 174 myPartitionSettings = thePartitionSettings; 175 } 176 177 @Override 178 public SearchParamSet<PathAndRef> extractResourceLinks(IBaseResource theResource, boolean theWantLocalReferences) { 179 IExtractor<PathAndRef> extractor = createReferenceExtractor(); 180 return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.REFERENCE, theWantLocalReferences); 181 } 182 183 private IExtractor<PathAndRef> createReferenceExtractor() { 184 return new ResourceLinkExtractor(); 185 } 186 187 @Override 188 public PathAndRef extractReferenceLinkFromResource(IBase theValue, String thePath) { 189 ResourceLinkExtractor extractor = new ResourceLinkExtractor(); 190 return extractor.get(theValue, thePath); 191 } 192 193 @Override 194 public List<String> extractParamValuesAsStrings(RuntimeSearchParam theSearchParam, IBaseResource theResource) { 195 IExtractor extractor; 196 switch (theSearchParam.getParamType()) { 197 case DATE: 198 extractor = createDateExtractor(theResource); 199 break; 200 case STRING: 201 extractor = createStringExtractor(theResource); 202 break; 203 case TOKEN: 204 extractor = createTokenExtractor(theResource); 205 break; 206 case NUMBER: 207 extractor = createNumberExtractor(theResource); 208 break; 209 case REFERENCE: 210 extractor = createReferenceExtractor(); 211 return extractReferenceParamsAsQueryTokens(theSearchParam, theResource, extractor); 212 case QUANTITY: 213 if (myModelConfig.getNormalizedQuantitySearchLevel().equals(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED)) { 214 extractor = new CompositeExtractor( 215 createQuantityExtractor(theResource), 216 createQuantityNormalizedExtractor(theResource) 217 ); 218 } else { 219 extractor = createQuantityExtractor(theResource); 220 } 221 break; 222 case URI: 223 extractor = createUriExtractor(theResource); 224 break; 225 case SPECIAL: 226 extractor = createSpecialExtractor(theResource.getIdElement().getResourceType()); 227 break; 228 case COMPOSITE: 229 default: 230 throw new UnsupportedOperationException(Msg.code(503) + "Type " + theSearchParam.getParamType() + " not supported for extraction"); 231 } 232 233 return extractParamsAsQueryTokens(theSearchParam, theResource, extractor); 234 } 235 236 private List<String> extractReferenceParamsAsQueryTokens(RuntimeSearchParam theSearchParam, IBaseResource theResource, IExtractor<PathAndRef> theExtractor) { 237 SearchParamSet<PathAndRef> params = new SearchParamSet<>(); 238 extractSearchParam(theSearchParam, theResource, theExtractor, params, false); 239 return refsToStringList(params); 240 } 241 242 private List<String> refsToStringList(SearchParamSet<PathAndRef> theParams) { 243 return theParams.stream() 244 .map(PathAndRef::getRef) 245 .map(ref -> ref.getReferenceElement().toUnqualifiedVersionless().getValue()) 246 .collect(Collectors.toList()); 247 } 248 249 private <T extends BaseResourceIndexedSearchParam> List<String> extractParamsAsQueryTokens(RuntimeSearchParam theSearchParam, IBaseResource theResource, IExtractor<T> theExtractor) { 250 SearchParamSet<T> params = new SearchParamSet<>(); 251 extractSearchParam(theSearchParam, theResource, theExtractor, params, false); 252 return toStringList(params); 253 } 254 255 private <T extends BaseResourceIndexedSearchParam> List<String> toStringList(SearchParamSet<T> theParams) { 256 return theParams.stream() 257 .map(param -> param.toQueryParameterType().getValueAsQueryToken(myContext)) 258 .collect(Collectors.toList()); 259 } 260 261 @Override 262 public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource) { 263 IExtractor<BaseResourceIndexedSearchParam> extractor = createTokenExtractor(theResource); 264 return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.TOKEN, false); 265 } 266 267 @Override 268 public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource, RuntimeSearchParam theSearchParam) { 269 IExtractor<BaseResourceIndexedSearchParam> extractor = createTokenExtractor(theResource); 270 SearchParamSet<BaseResourceIndexedSearchParam> setToPopulate = new SearchParamSet<>(); 271 extractSearchParam(theSearchParam, theResource, extractor, setToPopulate, false); 272 return setToPopulate; 273 } 274 275 private IExtractor<BaseResourceIndexedSearchParam> createTokenExtractor(IBaseResource theResource) { 276 String resourceTypeName = toRootTypeName(theResource); 277 String useSystem; 278 if (getContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2)) { 279 if (resourceTypeName.equals("ValueSet")) { 280 ca.uhn.fhir.model.dstu2.resource.ValueSet dstu2ValueSet = (ca.uhn.fhir.model.dstu2.resource.ValueSet) theResource; 281 useSystem = dstu2ValueSet.getCodeSystem().getSystem(); 282 } else { 283 useSystem = null; 284 } 285 } else { 286 if (resourceTypeName.equals("CodeSystem")) { 287 useSystem = extractValueAsString(myCodeSystemUrlValueChild, theResource); 288 } else { 289 useSystem = null; 290 } 291 } 292 293 return new TokenExtractor(resourceTypeName, useSystem); 294 } 295 296 @Override 297 public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamSpecial(IBaseResource theResource) { 298 String resourceTypeName = toRootTypeName(theResource); 299 IExtractor<BaseResourceIndexedSearchParam> extractor = createSpecialExtractor(resourceTypeName); 300 return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.SPECIAL, false); 301 } 302 303 private IExtractor<BaseResourceIndexedSearchParam> createSpecialExtractor(String theResourceTypeName) { 304 return (params, searchParam, value, path, theWantLocalReferences) -> { 305 if (COORDS_INDEX_PATHS.contains(path)) { 306 addCoords_Position(theResourceTypeName, params, searchParam, value); 307 } 308 }; 309 } 310 311 private void addUnexpectedDatatypeWarning(SearchParamSet<?> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 312 theParams.addWarning("Search param " + theSearchParam.getName() + " is of unexpected datatype: " + theValue.getClass()); 313 } 314 315 @Override 316 public SearchParamSet<ResourceIndexedSearchParamUri> extractSearchParamUri(IBaseResource theResource) { 317 IExtractor<ResourceIndexedSearchParamUri> extractor = createUriExtractor(theResource); 318 return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.URI, false); 319 } 320 321 private IExtractor<ResourceIndexedSearchParamUri> createUriExtractor(IBaseResource theResource) { 322 return (params, searchParam, value, path, theWantLocalReferences) -> { 323 String nextType = toRootTypeName(value); 324 String resourceType = toRootTypeName(theResource); 325 switch (nextType) { 326 case "uri": 327 case "url": 328 case "oid": 329 case "sid": 330 case "uuid": 331 addUri_Uri(resourceType, params, searchParam, value); 332 break; 333 default: 334 addUnexpectedDatatypeWarning(params, searchParam, value); 335 break; 336 } 337 }; 338 } 339 340 @Override 341 public SearchParamSet<ResourceIndexedSearchParamDate> extractSearchParamDates(IBaseResource theResource) { 342 IExtractor<ResourceIndexedSearchParamDate> extractor = createDateExtractor(theResource); 343 return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.DATE, false); 344 } 345 346 private IExtractor<ResourceIndexedSearchParamDate> createDateExtractor(IBaseResource theResource) { 347 return new DateExtractor(theResource); 348 } 349 350 @Override 351 public Date extractDateFromResource(IBase theValue, String thePath) { 352 DateExtractor extractor = new DateExtractor("DateType"); 353 return extractor.get(theValue, thePath, false).getValueHigh(); 354 } 355 356 @Override 357 public SearchParamSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(IBaseResource theResource) { 358 IExtractor<ResourceIndexedSearchParamNumber> extractor = createNumberExtractor(theResource); 359 return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.NUMBER, false); 360 } 361 362 private IExtractor<ResourceIndexedSearchParamNumber> createNumberExtractor(IBaseResource theResource) { 363 return (params, searchParam, value, path, theWantLocalReferences) -> { 364 String nextType = toRootTypeName(value); 365 String resourceType = toRootTypeName(theResource); 366 switch (nextType) { 367 case "Duration": 368 addNumber_Duration(resourceType, params, searchParam, value); 369 break; 370 case "Quantity": 371 addNumber_Quantity(resourceType, params, searchParam, value); 372 break; 373 case "integer": 374 case "positiveInt": 375 case "unsignedInt": 376 addNumber_Integer(resourceType, params, searchParam, value); 377 break; 378 case "decimal": 379 addNumber_Decimal(resourceType, params, searchParam, value); 380 break; 381 default: 382 addUnexpectedDatatypeWarning(params, searchParam, value); 383 break; 384 } 385 }; 386 } 387 388 @Override 389 public SearchParamSet<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(IBaseResource theResource) { 390 IExtractor<ResourceIndexedSearchParamQuantity> extractor = createQuantityExtractor(theResource); 391 return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY, false); 392 } 393 394 395 @Override 396 public SearchParamSet<ResourceIndexedSearchParamQuantityNormalized> extractSearchParamQuantityNormalized(IBaseResource theResource) { 397 IExtractor<ResourceIndexedSearchParamQuantityNormalized> extractor = createQuantityNormalizedExtractor(theResource); 398 return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY, false); 399 } 400 401 private IExtractor<ResourceIndexedSearchParamQuantity> createQuantityExtractor(IBaseResource theResource) { 402 return (params, searchParam, value, path, theWantLocalReferences) -> { 403 if (value.getClass().equals(myLocationPositionDefinition.getImplementingClass())) { 404 return; 405 } 406 407 String nextType = toRootTypeName(value); 408 String resourceType = toRootTypeName(theResource); 409 switch (nextType) { 410 case "Quantity": 411 addQuantity_Quantity(resourceType, params, searchParam, value); 412 break; 413 case "Money": 414 addQuantity_Money(resourceType, params, searchParam, value); 415 break; 416 case "Range": 417 addQuantity_Range(resourceType, params, searchParam, value); 418 break; 419 default: 420 addUnexpectedDatatypeWarning(params, searchParam, value); 421 break; 422 } 423 }; 424 } 425 426 private IExtractor<ResourceIndexedSearchParamQuantityNormalized> createQuantityNormalizedExtractor(IBaseResource theResource) { 427 428 return (params, searchParam, value, path, theWantLocalReferences) -> { 429 if (value.getClass().equals(myLocationPositionDefinition.getImplementingClass())) { 430 return; 431 } 432 433 String nextType = toRootTypeName(value); 434 String resourceType = toRootTypeName(theResource); 435 switch (nextType) { 436 case "Quantity": 437 addQuantity_QuantityNormalized(resourceType, params, searchParam, value); 438 break; 439 case "Money": 440 addQuantity_MoneyNormalized(resourceType, params, searchParam, value); 441 break; 442 case "Range": 443 addQuantity_RangeNormalized(resourceType, params, searchParam, value); 444 break; 445 default: 446 addUnexpectedDatatypeWarning(params, searchParam, value); 447 break; 448 } 449 }; 450 } 451 452 @Override 453 public SearchParamSet<ResourceIndexedSearchParamString> extractSearchParamStrings(IBaseResource theResource) { 454 IExtractor<ResourceIndexedSearchParamString> extractor = createStringExtractor(theResource); 455 456 return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.STRING, false); 457 } 458 459 private IExtractor<ResourceIndexedSearchParamString> createStringExtractor(IBaseResource theResource) { 460 return (params, searchParam, value, path, theWantLocalReferences) -> { 461 String resourceType = toRootTypeName(theResource); 462 463 if (value instanceof IPrimitiveType) { 464 IPrimitiveType<?> nextValue = (IPrimitiveType<?>) value; 465 String valueAsString = nextValue.getValueAsString(); 466 createStringIndexIfNotBlank(resourceType, params, searchParam, valueAsString); 467 return; 468 } 469 470 String nextType = toRootTypeName(value); 471 switch (nextType) { 472 case "HumanName": 473 addString_HumanName(resourceType, params, searchParam, value); 474 break; 475 case "Address": 476 addString_Address(resourceType, params, searchParam, value); 477 break; 478 case "ContactPoint": 479 addString_ContactPoint(resourceType, params, searchParam, value); 480 break; 481 case "Quantity": 482 addString_Quantity(resourceType, params, searchParam, value); 483 break; 484 case "Range": 485 addString_Range(resourceType, params, searchParam, value); 486 break; 487 default: 488 addUnexpectedDatatypeWarning(params, searchParam, value); 489 break; 490 } 491 }; 492 } 493 494 /** 495 * Override parent because we're using FHIRPath here 496 */ 497 @Override 498 public List<IBase> extractValues(String thePaths, IBaseResource theResource) { 499 List<IBase> values = new ArrayList<>(); 500 if (isNotBlank(thePaths)) { 501 String[] nextPathsSplit = split(thePaths); 502 for (String nextPath : nextPathsSplit) { 503 List<? extends IBase> allValues; 504 505 // This path is hard to parse and isn't likely to produce anything useful anyway 506 if (myContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU2)) { 507 if (nextPath.equals("Bundle.entry.resource(0)")) { 508 continue; 509 } 510 } 511 512 nextPath = trim(nextPath); 513 IValueExtractor allValuesFunc = getPathValueExtractor(theResource, nextPath); 514 try { 515 allValues = allValuesFunc.get(); 516 } catch (Exception e) { 517 String msg = getContext().getLocalizer().getMessage(BaseSearchParamExtractor.class, "failedToExtractPaths", nextPath, e.toString()); 518 throw new InternalErrorException(Msg.code(504) + msg, e); 519 } 520 521 values.addAll(allValues); 522 } 523 524 for (int i = 0; i < values.size(); i++) { 525 IBase nextObject = values.get(i); 526 if (nextObject instanceof IBaseExtension) { 527 IBaseExtension nextExtension = (IBaseExtension) nextObject; 528 nextObject = nextExtension.getValue(); 529 values.set(i, nextObject); 530 } 531 } 532 } 533 534 return values; 535 } 536 537 protected FhirContext getContext() { 538 return myContext; 539 } 540 541 @VisibleForTesting 542 public void setContext(FhirContext theContext) { 543 myContext = theContext; 544 } 545 546 protected ModelConfig getModelConfig() { 547 return myModelConfig; 548 } 549 550 @VisibleForTesting 551 public void setModelConfig(ModelConfig theModelConfig) { 552 myModelConfig = theModelConfig; 553 } 554 555 @VisibleForTesting 556 public void setSearchParamRegistry(ISearchParamRegistry theSearchParamRegistry) { 557 mySearchParamRegistry = theSearchParamRegistry; 558 } 559 560 private Collection<RuntimeSearchParam> getSearchParams(IBaseResource theResource) { 561 RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); 562 Collection<RuntimeSearchParam> retVal = mySearchParamRegistry.getActiveSearchParams(def.getName()).values(); 563 List<RuntimeSearchParam> defaultList = Collections.emptyList(); 564 retVal = ObjectUtils.defaultIfNull(retVal, defaultList); 565 return retVal; 566 } 567 568 private void addQuantity_Quantity(String theResourceType, Set<ResourceIndexedSearchParamQuantity> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 569 Optional<IPrimitiveType<BigDecimal>> valueField = myQuantityValueValueChild.getAccessor().getFirstValueOrNull(theValue); 570 if (valueField.isPresent() && valueField.get().getValue() != null) { 571 BigDecimal nextValueValue = valueField.get().getValue(); 572 String system = extractValueAsString(myQuantitySystemValueChild, theValue); 573 String code = extractValueAsString(myQuantityCodeValueChild, theValue); 574 575 ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(myPartitionSettings, theResourceType, theSearchParam.getName(), nextValueValue, system, code); 576 577 theParams.add(nextEntity); 578 } 579 } 580 581 private void addQuantity_QuantityNormalized(String theResourceType, Set<ResourceIndexedSearchParamQuantityNormalized> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 582 Optional<IPrimitiveType<BigDecimal>> valueField = myQuantityValueValueChild.getAccessor().getFirstValueOrNull(theValue); 583 if (valueField.isPresent() && valueField.get().getValue() != null) { 584 BigDecimal nextValueValue = valueField.get().getValue(); 585 String system = extractValueAsString(myQuantitySystemValueChild, theValue); 586 String code = extractValueAsString(myQuantityCodeValueChild, theValue); 587 588 //-- convert the value/unit to the canonical form if any 589 Pair canonicalForm = UcumServiceUtil.getCanonicalForm(system, nextValueValue, code); 590 if (canonicalForm != null) { 591 double canonicalValue = Double.parseDouble(canonicalForm.getValue().asDecimal()); 592 String canonicalUnits = canonicalForm.getCode(); 593 ResourceIndexedSearchParamQuantityNormalized nextEntity = new ResourceIndexedSearchParamQuantityNormalized(myPartitionSettings, theResourceType, theSearchParam.getName(), canonicalValue, system, canonicalUnits); 594 theParams.add(nextEntity); 595 } 596 597 } 598 } 599 600 private void addQuantity_Money(String theResourceType, Set<ResourceIndexedSearchParamQuantity> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 601 Optional<IPrimitiveType<BigDecimal>> valueField = myMoneyValueChild.getAccessor().getFirstValueOrNull(theValue); 602 if (valueField.isPresent() && valueField.get().getValue() != null) { 603 BigDecimal nextValueValue = valueField.get().getValue(); 604 605 String nextValueString = "urn:iso:std:iso:4217"; 606 String nextValueCode = extractValueAsString(myMoneyCurrencyChild, theValue); 607 String searchParamName = theSearchParam.getName(); 608 609 ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(myPartitionSettings, theResourceType, searchParamName, nextValueValue, nextValueString, nextValueCode); 610 theParams.add(nextEntity); 611 } 612 } 613 614 private void addQuantity_MoneyNormalized(String theResourceType, Set<ResourceIndexedSearchParamQuantityNormalized> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 615 Optional<IPrimitiveType<BigDecimal>> valueField = myMoneyValueChild.getAccessor().getFirstValueOrNull(theValue); 616 if (valueField.isPresent() && valueField.get().getValue() != null) { 617 BigDecimal nextValueValue = valueField.get().getValue(); 618 619 String nextValueString = "urn:iso:std:iso:4217"; 620 String nextValueCode = extractValueAsString(myMoneyCurrencyChild, theValue); 621 String searchParamName = theSearchParam.getName(); 622 623 ResourceIndexedSearchParamQuantityNormalized nextEntityNormalized = new ResourceIndexedSearchParamQuantityNormalized(myPartitionSettings, theResourceType, searchParamName, nextValueValue.doubleValue(), nextValueString, nextValueCode); 624 theParams.add(nextEntityNormalized); 625 } 626 } 627 628 private void addQuantity_Range(String theResourceType, Set<ResourceIndexedSearchParamQuantity> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 629 Optional<IBase> low = myRangeLowValueChild.getAccessor().getFirstValueOrNull(theValue); 630 low.ifPresent(theIBase -> addQuantity_Quantity(theResourceType, theParams, theSearchParam, theIBase)); 631 632 Optional<IBase> high = myRangeHighValueChild.getAccessor().getFirstValueOrNull(theValue); 633 high.ifPresent(theIBase -> addQuantity_Quantity(theResourceType, theParams, theSearchParam, theIBase)); 634 } 635 636 private void addQuantity_RangeNormalized(String theResourceType, Set<ResourceIndexedSearchParamQuantityNormalized> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 637 Optional<IBase> low = myRangeLowValueChild.getAccessor().getFirstValueOrNull(theValue); 638 low.ifPresent(theIBase -> addQuantity_QuantityNormalized(theResourceType, theParams, theSearchParam, theIBase)); 639 640 Optional<IBase> high = myRangeHighValueChild.getAccessor().getFirstValueOrNull(theValue); 641 high.ifPresent(theIBase -> addQuantity_QuantityNormalized(theResourceType, theParams, theSearchParam, theIBase)); 642 } 643 644 @SuppressWarnings("unchecked") 645 private void addToken_Identifier(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 646 String system = extractValueAsString(myIdentifierSystemValueChild, theValue); 647 String value = extractValueAsString(myIdentifierValueValueChild, theValue); 648 if (isNotBlank(value)) { 649 createTokenIndexIfNotBlankAndAdd(theResourceType, theParams, theSearchParam, system, value); 650 651 boolean indexIdentifierType = myModelConfig.isIndexIdentifierOfType(); 652 if (indexIdentifierType) { 653 Optional<IBase> type = myIdentifierTypeValueChild.getAccessor().getFirstValueOrNull(theValue); 654 if (type.isPresent()) { 655 List<IBase> codings = myCodeableConceptCodingValueChild.getAccessor().getValues(type.get()); 656 for (IBase nextCoding : codings) { 657 658 String typeSystem = myCodingSystemValueChild.getAccessor().getFirstValueOrNull(nextCoding).map(t -> ((IPrimitiveType<String>) t).getValue()).orElse(null); 659 String typeValue = myCodingCodeValueChild.getAccessor().getFirstValueOrNull(nextCoding).map(t -> ((IPrimitiveType<String>) t).getValue()).orElse(null); 660 if (isNotBlank(typeSystem) && isNotBlank(typeValue)) { 661 String paramName = theSearchParam.getName() + Constants.PARAMQUALIFIER_TOKEN_OF_TYPE; 662 ResourceIndexedSearchParamToken token = createTokenIndexIfNotBlank(theResourceType, typeSystem, typeValue + "|" + value, paramName); 663 if (token != null) { 664 theParams.add(token); 665 } 666 } 667 668 } 669 } 670 } 671 } 672 673 } 674 675 protected boolean shouldIndexTextComponentOfToken(RuntimeSearchParam theSearchParam) { 676 return tokenTextIndexingEnabledForSearchParam(myModelConfig, theSearchParam); 677 } 678 679 private void addToken_CodeableConcept(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 680 List<IBase> codings = getCodingsFromCodeableConcept(theValue); 681 for (IBase nextCoding : codings) { 682 addToken_Coding(theResourceType, theParams, theSearchParam, nextCoding); 683 } 684 685 if (shouldIndexTextComponentOfToken(theSearchParam)) { 686 String text = getDisplayTextFromCodeableConcept(theValue); 687 if (isNotBlank(text)) { 688 createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, text); 689 } 690 } 691 } 692 693 @Override 694 public List<IBase> getCodingsFromCodeableConcept(IBase theValue) { 695 String nextType = BaseSearchParamExtractor.this.toRootTypeName(theValue); 696 if ("CodeableConcept".equals(nextType)) { 697 return myCodeableConceptCodingValueChild.getAccessor().getValues(theValue); 698 } else { 699 return null; 700 } 701 } 702 703 @Override 704 public String getDisplayTextFromCodeableConcept(IBase theValue) { 705 String nextType = BaseSearchParamExtractor.this.toRootTypeName(theValue); 706 if ("CodeableConcept".equals(nextType)) { 707 return extractValueAsString(myCodeableConceptTextValueChild, theValue); 708 } else { 709 return null; 710 } 711 } 712 713 private void addToken_Coding(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 714 ResourceIndexedSearchParamToken resourceIndexedSearchParamToken = createSearchParamForCoding(theResourceType, theSearchParam, theValue); 715 if (resourceIndexedSearchParamToken != null) { 716 theParams.add(resourceIndexedSearchParamToken); 717 } 718 719 if (shouldIndexTextComponentOfToken(theSearchParam)) { 720 String text = getDisplayTextForCoding(theValue); 721 createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, text); 722 } 723 } 724 725 @Override 726 public ResourceIndexedSearchParamToken createSearchParamForCoding(String theResourceType, RuntimeSearchParam theSearchParam, IBase theValue) { 727 String nextType = BaseSearchParamExtractor.this.toRootTypeName(theValue); 728 if ("Coding".equals(nextType)) { 729 String system = extractValueAsString(myCodingSystemValueChild, theValue); 730 String code = extractValueAsString(myCodingCodeValueChild, theValue); 731 return createTokenIndexIfNotBlank(theResourceType, system, code, theSearchParam.getName()); 732 } else { 733 return null; 734 } 735 } 736 737 @Override 738 public String getDisplayTextForCoding(IBase theValue) { 739 String nextType = BaseSearchParamExtractor.this.toRootTypeName(theValue); 740 if ("Coding".equals(nextType)) { 741 return extractValueAsString(myCodingDisplayValueChild, theValue); 742 } else { 743 return null; 744 } 745 } 746 747 private void addToken_ContactPoint(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 748 String system = extractValueAsString(myContactPointSystemValueChild, theValue); 749 String value = extractValueAsString(myContactPointValueValueChild, theValue); 750 createTokenIndexIfNotBlankAndAdd(theResourceType, theParams, theSearchParam, system, value); 751 } 752 753 private void addToken_PatientCommunication(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 754 List<IBase> values = myPatientCommunicationLanguageValueChild.getAccessor().getValues(theValue); 755 for (IBase next : values) { 756 addToken_CodeableConcept(theResourceType, theParams, theSearchParam, next); 757 } 758 } 759 760 private void addToken_CapabilityStatementRestSecurity(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 761 List<IBase> values = myCapabilityStatementRestSecurityServiceValueChild.getAccessor().getValues(theValue); 762 for (IBase nextValue : values) { 763 addToken_CodeableConcept(theResourceType, theParams, theSearchParam, nextValue); 764 } 765 } 766 767 private void addDate_Period(String theResourceType, Set<ResourceIndexedSearchParamDate> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 768 Date start = extractValueAsDate(myPeriodStartValueChild, theValue); 769 String startAsString = extractValueAsString(myPeriodStartValueChild, theValue); 770 Date end = extractValueAsDate(myPeriodEndValueChild, theValue); 771 String endAsString = extractValueAsString(myPeriodEndValueChild, theValue); 772 773 if (start != null || end != null) { 774 775 if (start == null) { 776 start = myModelConfig.getPeriodIndexStartOfTime().getValue(); 777 startAsString = myModelConfig.getPeriodIndexStartOfTime().getValueAsString(); 778 } 779 if (end == null) { 780 end = myModelConfig.getPeriodIndexEndOfTime().getValue(); 781 endAsString = myModelConfig.getPeriodIndexEndOfTime().getValueAsString(); 782 } 783 784 ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(myPartitionSettings, theResourceType, theSearchParam.getName(), start, startAsString, end, endAsString, startAsString); 785 theParams.add(nextEntity); 786 } 787 } 788 789 private void addDate_Timing(String theResourceType, Set<ResourceIndexedSearchParamDate> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 790 List<IPrimitiveType<Date>> values = extractValuesAsFhirDates(myTimingEventValueChild, theValue); 791 792 TreeSet<Date> dates = new TreeSet<>(); 793 TreeSet<String> dateStrings = new TreeSet<>(); 794 String firstValue = null; 795 String finalValue = null; 796 for (IPrimitiveType<Date> nextEvent : values) { 797 if (nextEvent.getValue() != null) { 798 dates.add(nextEvent.getValue()); 799 if (firstValue == null) { 800 firstValue = nextEvent.getValueAsString(); 801 } 802 finalValue = nextEvent.getValueAsString(); 803 } 804 } 805 806 Optional<IBase> repeat = myTimingRepeatValueChild.getAccessor().getFirstValueOrNull(theValue); 807 if (repeat.isPresent()) { 808 Optional<IBase> bounds = myTimingRepeatBoundsValueChild.getAccessor().getFirstValueOrNull(repeat.get()); 809 if (bounds.isPresent()) { 810 String boundsType = toRootTypeName(bounds.get()); 811 if ("Period".equals(boundsType)) { 812 Date start = extractValueAsDate(myPeriodStartValueChild, bounds.get()); 813 Date end = extractValueAsDate(myPeriodEndValueChild, bounds.get()); 814 String endString = extractValueAsString(myPeriodEndValueChild, bounds.get()); 815 dates.add(start); 816 dates.add(end); 817 //TODO Check if this logic is valid. Does the start of the first period indicate a lower bound?? 818 if (firstValue == null) { 819 firstValue = extractValueAsString(myPeriodStartValueChild, bounds.get()); 820 } 821 finalValue = endString; 822 } 823 } 824 } 825 826 if (!dates.isEmpty()) { 827 ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(myPartitionSettings, theResourceType, theSearchParam.getName(), dates.first(), firstValue, dates.last(), finalValue, firstValue); 828 theParams.add(nextEntity); 829 } 830 } 831 832 private void addNumber_Duration(String theResourceType, Set<ResourceIndexedSearchParamNumber> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 833 String system = extractValueAsString(myDurationSystemValueChild, theValue); 834 String code = extractValueAsString(myDurationCodeValueChild, theValue); 835 BigDecimal value = extractValueAsBigDecimal(myDurationValueValueChild, theValue); 836 if (value != null) { 837 value = normalizeQuantityContainingTimeUnitsIntoDaysForNumberParam(system, code, value); 838 ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(myPartitionSettings, theResourceType, theSearchParam.getName(), value); 839 theParams.add(nextEntity); 840 } 841 } 842 843 private void addNumber_Quantity(String theResourceType, Set<ResourceIndexedSearchParamNumber> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 844 BigDecimal value = extractValueAsBigDecimal(myQuantityValueValueChild, theValue); 845 if (value != null) { 846 String system = extractValueAsString(myQuantitySystemValueChild, theValue); 847 String code = extractValueAsString(myQuantityCodeValueChild, theValue); 848 value = normalizeQuantityContainingTimeUnitsIntoDaysForNumberParam(system, code, value); 849 ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(myPartitionSettings, theResourceType, theSearchParam.getName(), value); 850 theParams.add(nextEntity); 851 } 852 } 853 854 @SuppressWarnings("unchecked") 855 private void addNumber_Integer(String theResourceType, Set<ResourceIndexedSearchParamNumber> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 856 IPrimitiveType<Integer> value = (IPrimitiveType<Integer>) theValue; 857 if (value.getValue() != null) { 858 BigDecimal valueDecimal = new BigDecimal(value.getValue()); 859 ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(myPartitionSettings, theResourceType, theSearchParam.getName(), valueDecimal); 860 theParams.add(nextEntity); 861 } 862 863 } 864 865 @SuppressWarnings("unchecked") 866 private void addNumber_Decimal(String theResourceType, Set<ResourceIndexedSearchParamNumber> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 867 IPrimitiveType<BigDecimal> value = (IPrimitiveType<BigDecimal>) theValue; 868 if (value.getValue() != null) { 869 BigDecimal valueDecimal = value.getValue(); 870 ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(myPartitionSettings, theResourceType, theSearchParam.getName(), valueDecimal); 871 theParams.add(nextEntity); 872 } 873 874 } 875 876 private void addCoords_Position(String theResourceType, SearchParamSet<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 877 BigDecimal latitude = null; 878 BigDecimal longitude = null; 879 880 if (theValue instanceof org.hl7.fhir.dstu3.model.Location.LocationPositionComponent) { 881 org.hl7.fhir.dstu3.model.Location.LocationPositionComponent value = (org.hl7.fhir.dstu3.model.Location.LocationPositionComponent) theValue; 882 latitude = value.getLatitude(); 883 longitude = value.getLongitude(); 884 } else if (theValue instanceof org.hl7.fhir.r4.model.Location.LocationPositionComponent) { 885 org.hl7.fhir.r4.model.Location.LocationPositionComponent value = (org.hl7.fhir.r4.model.Location.LocationPositionComponent) theValue; 886 latitude = value.getLatitude(); 887 longitude = value.getLongitude(); 888 } else if (theValue instanceof org.hl7.fhir.r5.model.Location.LocationPositionComponent) { 889 org.hl7.fhir.r5.model.Location.LocationPositionComponent value = (org.hl7.fhir.r5.model.Location.LocationPositionComponent) theValue; 890 latitude = value.getLatitude(); 891 longitude = value.getLongitude(); 892 } 893 // We only accept coordinates when both are present 894 if (latitude != null && longitude != null) { 895 double normalizedLatitude = GeopointNormalizer.normalizeLatitude(latitude.doubleValue()); 896 double normalizedLongitude = GeopointNormalizer.normalizeLongitude(longitude.doubleValue()); 897 ResourceIndexedSearchParamCoords nextEntity = new ResourceIndexedSearchParamCoords(myPartitionSettings, theResourceType, theSearchParam.getName(), normalizedLatitude, normalizedLongitude); 898 theParams.add(nextEntity); 899 } 900 } 901 902 private void addString_HumanName(String theResourceType, Set<ResourceIndexedSearchParamString> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 903 List<BaseRuntimeChildDefinition> myHumanNameChildren = Arrays.asList(myHumanNameFamilyValueChild, myHumanNameGivenValueChild, myHumanNameTextValueChild, myHumanNamePrefixValueChild, myHumanNameSuffixValueChild); 904 for (BaseRuntimeChildDefinition theChild : myHumanNameChildren) { 905 List<String> indices = extractValuesAsStrings(theChild, theValue); 906 for (String next : indices) { 907 createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, next); 908 } 909 } 910 } 911 912 private void addString_Quantity(String theResourceType, Set<ResourceIndexedSearchParamString> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 913 BigDecimal value = extractValueAsBigDecimal(myQuantityValueValueChild, theValue); 914 if (value != null) { 915 createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, value.toPlainString()); 916 } 917 } 918 919 private void addString_Range(String theResourceType, Set<ResourceIndexedSearchParamString> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 920 921 BigDecimal value = extractValueAsBigDecimal(myRangeLowValueChild, theValue); 922 if (value != null) { 923 createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, value.toPlainString()); 924 } 925 } 926 927 private void addString_ContactPoint(String theResourceType, Set<ResourceIndexedSearchParamString> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 928 929 String value = extractValueAsString(myContactPointValueValueChild, theValue); 930 if (isNotBlank(value)) { 931 createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, value); 932 } 933 } 934 935 private void addString_Address(String theResourceType, Set<ResourceIndexedSearchParamString> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 936 937 List<String> allNames = new ArrayList<>(extractValuesAsStrings(myAddressLineValueChild, theValue)); 938 939 String city = extractValueAsString(myAddressCityValueChild, theValue); 940 if (isNotBlank(city)) { 941 allNames.add(city); 942 } 943 944 String state = extractValueAsString(myAddressStateValueChild, theValue); 945 if (isNotBlank(state)) { 946 allNames.add(state); 947 } 948 949 String country = extractValueAsString(myAddressCountryValueChild, theValue); 950 if (isNotBlank(country)) { 951 allNames.add(country); 952 } 953 954 String postalCode = extractValueAsString(myAddressPostalCodeValueChild, theValue); 955 if (isNotBlank(postalCode)) { 956 allNames.add(postalCode); 957 } 958 959 for (String nextName : allNames) { 960 createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, nextName); 961 } 962 963 } 964 965 private <T> SearchParamSet<T> extractSearchParams(IBaseResource theResource, IExtractor<T> theExtractor, RestSearchParameterTypeEnum theSearchParamType, boolean theWantLocalReferences) { 966 SearchParamSet<T> retVal = new SearchParamSet<>(); 967 968 Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource); 969 970 cleanUpContainedResourceReferences(theResource, theSearchParamType, searchParams); 971 972 for (RuntimeSearchParam nextSpDef : searchParams) { 973 if (nextSpDef.getParamType() != theSearchParamType) { 974 continue; 975 } 976 977 extractSearchParam(nextSpDef, theResource, theExtractor, retVal, theWantLocalReferences); 978 } 979 return retVal; 980 } 981 982 983 /** 984 * Helper function to determine if a set of SPs for a resource uses a resolve as part of its fhir path. 985 */ 986 private boolean anySearchParameterUsesResolve(Collection<RuntimeSearchParam> searchParams, RestSearchParameterTypeEnum theSearchParamType) { 987 return searchParams.stream() 988 .filter(param -> param.getParamType() != theSearchParamType) 989 .map(RuntimeSearchParam::getPath) 990 .filter(Objects::nonNull) 991 .anyMatch(path -> path.contains("resolve")); 992 } 993 994 /** 995 * HAPI FHIR Reference objects (e.g. {@link org.hl7.fhir.r4.model.Reference}) can hold references either by text 996 * (e.g. "#3") or by resource (e.g. "new Reference(patientInstance)"). The FHIRPath evaluator only understands the 997 * first way, so if there is any chance of the FHIRPath evaluator needing to descend across references, we 998 * have to assign values to those references before indexing. 999 * <p> 1000 * Doing this cleanup isn't hugely expensive, but it's not completely free either so we only do it 1001 * if we think there's actually a chance 1002 */ 1003 private void cleanUpContainedResourceReferences(IBaseResource theResource, RestSearchParameterTypeEnum theSearchParamType, Collection<RuntimeSearchParam> searchParams) { 1004 boolean havePathWithResolveExpression = 1005 myModelConfig.isIndexOnContainedResources() 1006 || anySearchParameterUsesResolve(searchParams, theSearchParamType); 1007 1008 if (havePathWithResolveExpression && myContext.getParserOptions().isAutoContainReferenceTargetsWithNoId()) { 1009 //TODO GGG/JA: At this point, if the Task.basedOn.reference.resource does _not_ have an ID, we will attempt to contain it internally. Wild 1010 myContext.newTerser().containResources(theResource, FhirTerser.OptionsEnum.MODIFY_RESOURCE, FhirTerser.OptionsEnum.STORE_AND_REUSE_RESULTS); 1011 } 1012 } 1013 1014 private <T> void extractSearchParam(RuntimeSearchParam theSearchParameterDef, IBaseResource theResource, IExtractor<T> theExtractor, SearchParamSet<T> theSetToPopulate, boolean theWantLocalReferences) { 1015 String nextPathUnsplit = theSearchParameterDef.getPath(); 1016 if (isBlank(nextPathUnsplit)) { 1017 return; 1018 } 1019 1020 String[] splitPaths = split(nextPathUnsplit); 1021 for (String nextPath : splitPaths) { 1022 nextPath = trim(nextPath); 1023 for (IBase nextObject : extractValues(nextPath, theResource)) { 1024 if (nextObject != null) { 1025 String typeName = toRootTypeName(nextObject); 1026 if (!myIgnoredForSearchDatatypes.contains(typeName)) { 1027 theExtractor.extract(theSetToPopulate, theSearchParameterDef, nextObject, nextPath, theWantLocalReferences); 1028 } 1029 } 1030 } 1031 } 1032 } 1033 1034 @Override 1035 public String toRootTypeName(IBase nextObject) { 1036 BaseRuntimeElementDefinition<?> elementDefinition = getContext().getElementDefinition(nextObject.getClass()); 1037 BaseRuntimeElementDefinition<?> rootParentDefinition = elementDefinition.getRootParentDefinition(); 1038 return rootParentDefinition.getName(); 1039 } 1040 1041 @Override 1042 public String toTypeName(IBase nextObject) { 1043 BaseRuntimeElementDefinition<?> elementDefinition = getContext().getElementDefinition(nextObject.getClass()); 1044 return elementDefinition.getName(); 1045 } 1046 1047 private void addUri_Uri(String theResourceType, Set<ResourceIndexedSearchParamUri> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 1048 IPrimitiveType<?> value = (IPrimitiveType<?>) theValue; 1049 String valueAsString = value.getValueAsString(); 1050 if (isNotBlank(valueAsString)) { 1051 ResourceIndexedSearchParamUri nextEntity = new ResourceIndexedSearchParamUri(myPartitionSettings, theResourceType, theSearchParam.getName(), valueAsString); 1052 theParams.add(nextEntity); 1053 } 1054 } 1055 1056 @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"}) 1057 private void createStringIndexIfNotBlank(String theResourceType, Set<? extends BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, String theValue) { 1058 String value = theValue; 1059 if (isNotBlank(value)) { 1060 if (value.length() > ResourceIndexedSearchParamString.MAX_LENGTH) { 1061 value = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); 1062 } 1063 1064 String searchParamName = theSearchParam.getName(); 1065 String valueNormalized = StringUtil.normalizeStringForSearchIndexing(value); 1066 String valueEncoded = theSearchParam.encode(valueNormalized); 1067 1068 if (valueEncoded.length() > ResourceIndexedSearchParamString.MAX_LENGTH) { 1069 valueEncoded = valueEncoded.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); 1070 } 1071 1072 ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(myPartitionSettings, getModelConfig(), theResourceType, searchParamName, valueEncoded, value); 1073 1074 Set params = theParams; 1075 params.add(nextEntity); 1076 } 1077 } 1078 1079 private void createTokenIndexIfNotBlankAndAdd(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, String theSystem, String theValue) { 1080 ResourceIndexedSearchParamToken nextEntity = createTokenIndexIfNotBlank(theResourceType, theSystem, theValue, theSearchParam.getName()); 1081 if (nextEntity != null) { 1082 theParams.add(nextEntity); 1083 } 1084 } 1085 1086 @VisibleForTesting 1087 public void setPartitionSettings(PartitionSettings thePartitionSettings) { 1088 myPartitionSettings = thePartitionSettings; 1089 } 1090 1091 private ResourceIndexedSearchParamToken createTokenIndexIfNotBlank(String theResourceType, String theSystem, String theValue, String searchParamName) { 1092 String system = theSystem; 1093 String value = theValue; 1094 ResourceIndexedSearchParamToken nextEntity = null; 1095 if (isNotBlank(system) || isNotBlank(value)) { 1096 if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) { 1097 system = system.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH); 1098 } 1099 if (value != null && value.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) { 1100 value = value.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH); 1101 } 1102 1103 nextEntity = new ResourceIndexedSearchParamToken(myPartitionSettings, theResourceType, searchParamName, system, value); 1104 } 1105 1106 return nextEntity; 1107 } 1108 1109 @Override 1110 public String[] split(String thePaths) { 1111 if (shouldAttemptToSplitPath(thePaths)) { 1112 return splitOutOfParensOrs(thePaths); 1113 } else { 1114 return new String[]{thePaths}; 1115 } 1116 } 1117 1118 public boolean shouldAttemptToSplitPath(String thePath) { 1119 if (getContext().getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) { 1120 if (thePath.contains("|")) { 1121 return true; 1122 } 1123 } else { 1124 //DSTU 3 and below used "or" as well as "|" 1125 if (thePath.contains("|") || thePath.contains(" or ")) { 1126 return true; 1127 } 1128 } 1129 return false; 1130 } 1131 1132 /** 1133 * Iteratively splits a string on any ` or ` or | that is ** not** contained inside a set of parentheses. e.g. 1134 * 1135 * "Patient.select(a or b)" --> ["Patient.select(a or b)"] 1136 * "Patient.select(a or b) or Patient.select(c or d )" --> ["Patient.select(a or b)", "Patient.select(c or d)"] 1137 * "Patient.select(a|b) or Patient.select(c or d )" --> ["Patient.select(a|b)", "Patient.select(c or d)"] 1138 * "Patient.select(b) | Patient.select(c)" --> ["Patient.select(b)", "Patient.select(c)"] 1139 * 1140 * @param thePaths The string to split 1141 * @return The split string 1142 1143 */ 1144 private String[] splitOutOfParensOrs(String thePaths) { 1145 List<String> topLevelOrExpressions = splitOutOfParensToken(thePaths, " or "); 1146 List<String> retVal = topLevelOrExpressions.stream() 1147 .flatMap(s -> splitOutOfParensToken(s, " |").stream()) 1148 .collect(Collectors.toList()); 1149 return retVal.toArray(new String[retVal.size()]); 1150 } 1151 1152 private List<String> splitOutOfParensToken(String thePath, String theToken) { 1153 int tokenLength = theToken.length(); 1154 int index = thePath.indexOf(theToken); 1155 int rightIndex = 0; 1156 List<String> retVal = new ArrayList<>(); 1157 while (index > -1 ) { 1158 String left = thePath.substring(rightIndex, index); 1159 if (allParensHaveBeenClosed(left)) { 1160 retVal.add(left); 1161 rightIndex = index + tokenLength; 1162 } 1163 index = thePath.indexOf(theToken, index + tokenLength); 1164 } 1165 retVal.add(thePath.substring(rightIndex)); 1166 return retVal; 1167 } 1168 1169 private boolean allParensHaveBeenClosed(String thePaths) { 1170 int open = StringUtils.countMatches(thePaths, "("); 1171 int close = StringUtils.countMatches(thePaths, ")"); 1172 return open == close; 1173 } 1174 1175 private BigDecimal normalizeQuantityContainingTimeUnitsIntoDaysForNumberParam(String theSystem, String theCode, BigDecimal theValue) { 1176 if (SearchParamConstants.UCUM_NS.equals(theSystem)) { 1177 if (isNotBlank(theCode)) { 1178 Unit<? extends Quantity> unit = Unit.valueOf(theCode); 1179 javax.measure.converter.UnitConverter dayConverter = unit.getConverterTo(NonSI.DAY); 1180 double dayValue = dayConverter.convert(theValue.doubleValue()); 1181 theValue = new BigDecimal(dayValue); 1182 } 1183 } 1184 return theValue; 1185 } 1186 1187 @PostConstruct 1188 public void start() { 1189 myIgnoredForSearchDatatypes = new HashSet<>(); 1190 addIgnoredType(getContext(), "Annotation", myIgnoredForSearchDatatypes); 1191 addIgnoredType(getContext(), "Attachment", myIgnoredForSearchDatatypes); 1192 addIgnoredType(getContext(), "Count", myIgnoredForSearchDatatypes); 1193 addIgnoredType(getContext(), "Distance", myIgnoredForSearchDatatypes); 1194 addIgnoredType(getContext(), "Ratio", myIgnoredForSearchDatatypes); 1195 addIgnoredType(getContext(), "SampledData", myIgnoredForSearchDatatypes); 1196 addIgnoredType(getContext(), "Signature", myIgnoredForSearchDatatypes); 1197 1198 /* 1199 * This is building up an internal map of all the various field accessors we'll need in order to work 1200 * with the model. This is kind of ugly, but we want to be as efficient as possible since 1201 * search param extraction happens a whole heck of a lot at runtime.. 1202 */ 1203 1204 BaseRuntimeElementCompositeDefinition<?> quantityDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Quantity"); 1205 myQuantityValueValueChild = quantityDefinition.getChildByName("value"); 1206 myQuantitySystemValueChild = quantityDefinition.getChildByName("system"); 1207 myQuantityCodeValueChild = quantityDefinition.getChildByName("code"); 1208 1209 BaseRuntimeElementCompositeDefinition<?> moneyDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Money"); 1210 myMoneyValueChild = moneyDefinition.getChildByName("value"); 1211 myMoneyCurrencyChild = moneyDefinition.getChildByName("currency"); 1212 1213 BaseRuntimeElementCompositeDefinition<?> locationDefinition = getContext().getResourceDefinition("Location"); 1214 BaseRuntimeChildDefinition locationPositionValueChild = locationDefinition.getChildByName("position"); 1215 myLocationPositionDefinition = (BaseRuntimeElementCompositeDefinition<?>) locationPositionValueChild.getChildByName("position"); 1216 1217 BaseRuntimeElementCompositeDefinition<?> codeSystemDefinition; 1218 if (getContext().getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { 1219 codeSystemDefinition = getContext().getResourceDefinition("CodeSystem"); 1220 assert codeSystemDefinition != null; 1221 myCodeSystemUrlValueChild = codeSystemDefinition.getChildByName("url"); 1222 } 1223 1224 BaseRuntimeElementCompositeDefinition<?> rangeDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Range"); 1225 myRangeLowValueChild = rangeDefinition.getChildByName("low"); 1226 myRangeHighValueChild = rangeDefinition.getChildByName("high"); 1227 1228 BaseRuntimeElementCompositeDefinition<?> addressDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Address"); 1229 myAddressLineValueChild = addressDefinition.getChildByName("line"); 1230 myAddressCityValueChild = addressDefinition.getChildByName("city"); 1231 myAddressStateValueChild = addressDefinition.getChildByName("state"); 1232 myAddressCountryValueChild = addressDefinition.getChildByName("country"); 1233 myAddressPostalCodeValueChild = addressDefinition.getChildByName("postalCode"); 1234 1235 if (getContext().getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { 1236 BaseRuntimeElementCompositeDefinition<?> capabilityStatementDefinition = getContext().getResourceDefinition("CapabilityStatement"); 1237 BaseRuntimeChildDefinition capabilityStatementRestChild = capabilityStatementDefinition.getChildByName("rest"); 1238 BaseRuntimeElementCompositeDefinition<?> capabilityStatementRestDefinition = (BaseRuntimeElementCompositeDefinition<?>) capabilityStatementRestChild.getChildByName("rest"); 1239 BaseRuntimeChildDefinition capabilityStatementRestSecurityValueChild = capabilityStatementRestDefinition.getChildByName("security"); 1240 BaseRuntimeElementCompositeDefinition<?> capabilityStatementRestSecurityDefinition = (BaseRuntimeElementCompositeDefinition<?>) capabilityStatementRestSecurityValueChild.getChildByName("security"); 1241 myCapabilityStatementRestSecurityServiceValueChild = capabilityStatementRestSecurityDefinition.getChildByName("service"); 1242 } 1243 1244 BaseRuntimeElementCompositeDefinition<?> periodDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Period"); 1245 myPeriodStartValueChild = periodDefinition.getChildByName("start"); 1246 myPeriodEndValueChild = periodDefinition.getChildByName("end"); 1247 1248 BaseRuntimeElementCompositeDefinition<?> timingDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Timing"); 1249 myTimingEventValueChild = timingDefinition.getChildByName("event"); 1250 myTimingRepeatValueChild = timingDefinition.getChildByName("repeat"); 1251 BaseRuntimeElementCompositeDefinition<?> timingRepeatDefinition = (BaseRuntimeElementCompositeDefinition<?>) myTimingRepeatValueChild.getChildByName("repeat"); 1252 myTimingRepeatBoundsValueChild = timingRepeatDefinition.getChildByName("bounds[x]"); 1253 1254 BaseRuntimeElementCompositeDefinition<?> durationDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Duration"); 1255 myDurationSystemValueChild = durationDefinition.getChildByName("system"); 1256 myDurationCodeValueChild = durationDefinition.getChildByName("code"); 1257 myDurationValueValueChild = durationDefinition.getChildByName("value"); 1258 1259 BaseRuntimeElementCompositeDefinition<?> humanNameDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("HumanName"); 1260 myHumanNameFamilyValueChild = humanNameDefinition.getChildByName("family"); 1261 myHumanNameGivenValueChild = humanNameDefinition.getChildByName("given"); 1262 myHumanNameTextValueChild = humanNameDefinition.getChildByName("text"); 1263 myHumanNamePrefixValueChild = humanNameDefinition.getChildByName("prefix"); 1264 myHumanNameSuffixValueChild = humanNameDefinition.getChildByName("suffix"); 1265 1266 BaseRuntimeElementCompositeDefinition<?> contactPointDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("ContactPoint"); 1267 myContactPointValueValueChild = contactPointDefinition.getChildByName("value"); 1268 myContactPointSystemValueChild = contactPointDefinition.getChildByName("system"); 1269 1270 BaseRuntimeElementCompositeDefinition<?> identifierDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Identifier"); 1271 myIdentifierSystemValueChild = identifierDefinition.getChildByName("system"); 1272 myIdentifierValueValueChild = identifierDefinition.getChildByName("value"); 1273 myIdentifierTypeValueChild = identifierDefinition.getChildByName("type"); 1274 BaseRuntimeElementCompositeDefinition<?> identifierTypeDefinition = (BaseRuntimeElementCompositeDefinition<?>) myIdentifierTypeValueChild.getChildByName("type"); 1275 myIdentifierTypeTextValueChild = identifierTypeDefinition.getChildByName("text"); 1276 1277 BaseRuntimeElementCompositeDefinition<?> codeableConceptDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("CodeableConcept"); 1278 myCodeableConceptCodingValueChild = codeableConceptDefinition.getChildByName("coding"); 1279 myCodeableConceptTextValueChild = codeableConceptDefinition.getChildByName("text"); 1280 1281 BaseRuntimeElementCompositeDefinition<?> codingDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Coding"); 1282 myCodingSystemValueChild = codingDefinition.getChildByName("system"); 1283 myCodingCodeValueChild = codingDefinition.getChildByName("code"); 1284 myCodingDisplayValueChild = codingDefinition.getChildByName("display"); 1285 1286 BaseRuntimeElementCompositeDefinition<?> patientDefinition = getContext().getResourceDefinition("Patient"); 1287 BaseRuntimeChildDefinition patientCommunicationValueChild = patientDefinition.getChildByName("communication"); 1288 BaseRuntimeElementCompositeDefinition<?> patientCommunicationDefinition = (BaseRuntimeElementCompositeDefinition<?>) patientCommunicationValueChild.getChildByName("communication"); 1289 myPatientCommunicationLanguageValueChild = patientCommunicationDefinition.getChildByName("language"); 1290 1291 } 1292 1293 @FunctionalInterface 1294 public interface IValueExtractor { 1295 1296 List<? extends IBase> get() throws FHIRException; 1297 1298 } 1299 1300 @FunctionalInterface 1301 private interface IExtractor<T> { 1302 1303 void extract(SearchParamSet<T> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath, boolean theWantLocalReferences); 1304 1305 } 1306 1307 /** 1308 * Note that this should only be called for R4+ servers. Prior to 1309 * R4 the paths could be separated by the word "or" or by a "|" 1310 * character, so we used a slower splitting mechanism. 1311 */ 1312 @Nonnull 1313 public static String[] splitPathsR4(@Nonnull String thePaths) { 1314 StringTokenizer tok = new StringTokenizer(thePaths, " |"); 1315 tok.setTrimmerMatcher(new StringTrimmingTrimmerMatcher()); 1316 return tok.getTokenArray(); 1317 } 1318 1319 public static boolean tokenTextIndexingEnabledForSearchParam(ModelConfig theModelConfig, RuntimeSearchParam theSearchParam) { 1320 Optional<Boolean> noSuppressForSearchParam = theSearchParam.getExtensions(HapiExtensions.EXT_SEARCHPARAM_TOKEN_SUPPRESS_TEXT_INDEXING).stream() 1321 .map(IBaseExtension::getValue) 1322 .map(val -> (IPrimitiveType<?>) val) 1323 .map(IPrimitiveType::getValueAsString) 1324 .map(Boolean::parseBoolean) 1325 .findFirst(); 1326 1327 //if the SP doesn't care, use the system default. 1328 if (!noSuppressForSearchParam.isPresent()) { 1329 return !theModelConfig.isSuppressStringIndexingInTokens(); 1330 //If the SP does care, use its value. 1331 } else { 1332 boolean suppressForSearchParam = noSuppressForSearchParam.get(); 1333 ourLog.trace("Text indexing for SearchParameter {}: {}", theSearchParam.getName(), suppressForSearchParam); 1334 return !suppressForSearchParam; 1335 } 1336 } 1337 1338 private static void addIgnoredType(FhirContext theCtx, String theType, Set<String> theIgnoredTypes) { 1339 BaseRuntimeElementDefinition<?> elementDefinition = theCtx.getElementDefinition(theType); 1340 if (elementDefinition != null) { 1341 theIgnoredTypes.add(elementDefinition.getName()); 1342 } 1343 } 1344 1345 protected static String extractValueAsString(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) { 1346 return theChildDefinition 1347 .getAccessor() 1348 .<IPrimitiveType<?>>getFirstValueOrNull(theElement) 1349 .map(IPrimitiveType::getValueAsString) 1350 .orElse(null); 1351 } 1352 1353 protected static Date extractValueAsDate(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) { 1354 return theChildDefinition 1355 .getAccessor() 1356 .<IPrimitiveType<Date>>getFirstValueOrNull(theElement) 1357 .map(IPrimitiveType::getValue) 1358 .orElse(null); 1359 } 1360 1361 protected static BigDecimal extractValueAsBigDecimal(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) { 1362 return theChildDefinition 1363 .getAccessor() 1364 .<IPrimitiveType<BigDecimal>>getFirstValueOrNull(theElement) 1365 .map(IPrimitiveType::getValue) 1366 .orElse(null); 1367 } 1368 1369 @SuppressWarnings("unchecked") 1370 protected static List<IPrimitiveType<Date>> extractValuesAsFhirDates(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) { 1371 return (List) theChildDefinition 1372 .getAccessor() 1373 .getValues(theElement); 1374 } 1375 1376 protected static List<String> extractValuesAsStrings(BaseRuntimeChildDefinition theChildDefinition, IBase theValue) { 1377 return theChildDefinition 1378 .getAccessor() 1379 .getValues(theValue) 1380 .stream() 1381 .map(t -> (IPrimitiveType) t) 1382 .map(IPrimitiveType::getValueAsString) 1383 .filter(StringUtils::isNotBlank) 1384 .collect(Collectors.toList()); 1385 } 1386 1387 protected static <T extends Enum<?>> String extractSystem(IBaseEnumeration<T> theBoundCode) { 1388 if (theBoundCode.getValue() != null) { 1389 return theBoundCode.getEnumFactory().toSystem(theBoundCode.getValue()); 1390 } 1391 return null; 1392 } 1393 1394 private class ResourceLinkExtractor implements IExtractor<PathAndRef> { 1395 1396 private PathAndRef myPathAndRef = null; 1397 1398 @Override 1399 public void extract(SearchParamSet<PathAndRef> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath, boolean theWantLocalReferences) { 1400 if (theValue instanceof IBaseResource) { 1401 return; 1402 } 1403 1404 String nextType = toRootTypeName(theValue); 1405 switch (nextType) { 1406 case "uri": 1407 case "canonical": 1408 String typeName = toTypeName(theValue); 1409 IPrimitiveType<?> valuePrimitive = (IPrimitiveType<?>) theValue; 1410 IBaseReference fakeReference = (IBaseReference) myContext.getElementDefinition("Reference").newInstance(); 1411 fakeReference.setReference(valuePrimitive.getValueAsString()); 1412 1413 // Canonical has a root type of "uri" 1414 if ("canonical".equals(typeName)) { 1415 1416 /* 1417 * See #1583 1418 * Technically canonical fields should not allow local references (e.g. 1419 * Questionnaire/123) but it seems reasonable for us to interpret a canonical 1420 * containing a local reference for what it is, and allow people to search 1421 * based on that. 1422 */ 1423 IIdType parsed = fakeReference.getReferenceElement(); 1424 if (parsed.hasIdPart() && parsed.hasResourceType() && !parsed.isAbsolute()) { 1425 myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, fakeReference, false); 1426 theParams.add(myPathAndRef); 1427 break; 1428 } 1429 1430 if (parsed.isAbsolute()) { 1431 myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, fakeReference, true); 1432 theParams.add(myPathAndRef); 1433 break; 1434 } 1435 } 1436 1437 theParams.addWarning("Ignoring canonical reference (indexing canonical is not yet supported)"); 1438 break; 1439 case "reference": 1440 case "Reference": 1441 IBaseReference valueRef = (IBaseReference) theValue; 1442 1443 IIdType nextId = valueRef.getReferenceElement(); 1444 if (nextId.isEmpty() && valueRef.getResource() != null) { 1445 nextId = valueRef.getResource().getIdElement(); 1446 } 1447 1448 if (nextId == null || 1449 nextId.isEmpty() || 1450 nextId.getValue().startsWith("urn:")) { 1451 return; 1452 } 1453 if (!theWantLocalReferences) { 1454 if (nextId.getValue().startsWith("#")) 1455 return; 1456 } 1457 1458 myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, valueRef, false); 1459 theParams.add(myPathAndRef); 1460 break; 1461 default: 1462 addUnexpectedDatatypeWarning(theParams, theSearchParam, theValue); 1463 break; 1464 } 1465 } 1466 1467 public PathAndRef get(IBase theValue, String thePath) { 1468 extract(new SearchParamSet<>(), 1469 new RuntimeSearchParam(null, null, "Reference", null, null, null, null, null, null, null), 1470 theValue, thePath, false); 1471 return myPathAndRef; 1472 } 1473 } 1474 1475 private class DateExtractor implements IExtractor<ResourceIndexedSearchParamDate> { 1476 1477 String myResourceType; 1478 ResourceIndexedSearchParamDate myIndexedSearchParamDate = null; 1479 1480 public DateExtractor(IBaseResource theResource) { 1481 myResourceType = toRootTypeName(theResource); 1482 } 1483 1484 public DateExtractor(String theResourceType) { 1485 myResourceType = theResourceType; 1486 } 1487 1488 @Override 1489 public void extract(SearchParamSet theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath, boolean theWantLocalReferences) { 1490 String nextType = toRootTypeName(theValue); 1491 switch (nextType) { 1492 case "date": 1493 case "dateTime": 1494 case "instant": 1495 addDateTimeTypes(myResourceType, theParams, theSearchParam, theValue); 1496 break; 1497 case "Period": 1498 addDate_Period(myResourceType, theParams, theSearchParam, theValue); 1499 break; 1500 case "Timing": 1501 addDate_Timing(myResourceType, theParams, theSearchParam, theValue); 1502 break; 1503 case "string": 1504 // CarePlan.activitydate can be a string - ignored for now 1505 break; 1506 default: 1507 addUnexpectedDatatypeWarning(theParams, theSearchParam, theValue); 1508 break; 1509 1510 } 1511 } 1512 1513 private void addDate_Period(String theResourceType, Set<ResourceIndexedSearchParamDate> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 1514 Date start = extractValueAsDate(myPeriodStartValueChild, theValue); 1515 String startAsString = extractValueAsString(myPeriodStartValueChild, theValue); 1516 Date end = extractValueAsDate(myPeriodEndValueChild, theValue); 1517 String endAsString = extractValueAsString(myPeriodEndValueChild, theValue); 1518 1519 if (start != null || end != null) { 1520 1521 if (start == null) { 1522 start = myModelConfig.getPeriodIndexStartOfTime().getValue(); 1523 startAsString = myModelConfig.getPeriodIndexStartOfTime().getValueAsString(); 1524 } 1525 if (end == null) { 1526 end = myModelConfig.getPeriodIndexEndOfTime().getValue(); 1527 endAsString = myModelConfig.getPeriodIndexEndOfTime().getValueAsString(); 1528 } 1529 1530 myIndexedSearchParamDate = new ResourceIndexedSearchParamDate(myPartitionSettings, theResourceType, theSearchParam.getName(), start, startAsString, end, endAsString, startAsString); 1531 theParams.add(myIndexedSearchParamDate); 1532 } 1533 } 1534 1535 private void addDate_Timing(String theResourceType, Set<ResourceIndexedSearchParamDate> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 1536 List<IPrimitiveType<Date>> values = extractValuesAsFhirDates(myTimingEventValueChild, theValue); 1537 1538 TreeSet<Date> dates = new TreeSet<>(); 1539 String firstValue = null; 1540 String finalValue = null; 1541 for (IPrimitiveType<Date> nextEvent : values) { 1542 if (nextEvent.getValue() != null) { 1543 dates.add(nextEvent.getValue()); 1544 if (firstValue == null) { 1545 firstValue = nextEvent.getValueAsString(); 1546 } 1547 finalValue = nextEvent.getValueAsString(); 1548 } 1549 } 1550 1551 Optional<IBase> repeat = myTimingRepeatValueChild.getAccessor().getFirstValueOrNull(theValue); 1552 if (repeat.isPresent()) { 1553 Optional<IBase> bounds = myTimingRepeatBoundsValueChild.getAccessor().getFirstValueOrNull(repeat.get()); 1554 if (bounds.isPresent()) { 1555 String boundsType = toRootTypeName(bounds.get()); 1556 if ("Period".equals(boundsType)) { 1557 Date start = extractValueAsDate(myPeriodStartValueChild, bounds.get()); 1558 Date end = extractValueAsDate(myPeriodEndValueChild, bounds.get()); 1559 if (start != null) { 1560 dates.add(start); 1561 } 1562 if (end != null) { 1563 dates.add(end); 1564 } 1565 } 1566 } 1567 } 1568 1569 if (!dates.isEmpty()) { 1570 myIndexedSearchParamDate = new ResourceIndexedSearchParamDate(myPartitionSettings, theResourceType, theSearchParam.getName(), dates.first(), firstValue, dates.last(), finalValue, firstValue); 1571 theParams.add(myIndexedSearchParamDate); 1572 } 1573 } 1574 1575 @SuppressWarnings("unchecked") 1576 private void addDateTimeTypes(String theResourceType, Set<ResourceIndexedSearchParamDate> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { 1577 IPrimitiveType<Date> nextBaseDateTime = (IPrimitiveType<Date>) theValue; 1578 if (nextBaseDateTime.getValue() != null) { 1579 myIndexedSearchParamDate = new ResourceIndexedSearchParamDate(myPartitionSettings, theResourceType, theSearchParam.getName(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString(), nextBaseDateTime.getValueAsString()); 1580 theParams.add(myIndexedSearchParamDate); 1581 } 1582 } 1583 1584 public ResourceIndexedSearchParamDate get(IBase theValue, String thePath, boolean theWantLocalReferences) { 1585 extract(new SearchParamSet<>(), 1586 new RuntimeSearchParam(null, null, "date", null, null, null, null, null, null, null), 1587 theValue, thePath, theWantLocalReferences); 1588 return myIndexedSearchParamDate; 1589 } 1590 } 1591 1592 private class TokenExtractor implements IExtractor<BaseResourceIndexedSearchParam> { 1593 private final String myResourceTypeName; 1594 private final String myUseSystem; 1595 1596 public TokenExtractor(String theResourceTypeName, String theUseSystem) { 1597 myResourceTypeName = theResourceTypeName; 1598 myUseSystem = theUseSystem; 1599 } 1600 1601 @Override 1602 public void extract(SearchParamSet<BaseResourceIndexedSearchParam> params, RuntimeSearchParam searchParam, IBase value, String path, boolean theWantLocalReferences) { 1603 1604 // DSTU3+ 1605 if (value instanceof IBaseEnumeration<?>) { 1606 IBaseEnumeration<?> obj = (IBaseEnumeration<?>) value; 1607 String system = extractSystem(obj); 1608 String code = obj.getValueAsString(); 1609 BaseSearchParamExtractor.this.createTokenIndexIfNotBlankAndAdd(myResourceTypeName, params, searchParam, system, code); 1610 return; 1611 } 1612 1613 // DSTU2 only 1614 if (value instanceof BoundCodeDt) { 1615 BoundCodeDt boundCode = (BoundCodeDt) value; 1616 Enum valueAsEnum = boundCode.getValueAsEnum(); 1617 String system = null; 1618 if (valueAsEnum != null) { 1619 //noinspection unchecked 1620 system = boundCode.getBinder().toSystemString(valueAsEnum); 1621 } 1622 String code = boundCode.getValueAsString(); 1623 BaseSearchParamExtractor.this.createTokenIndexIfNotBlankAndAdd(myResourceTypeName, params, searchParam, system, code); 1624 return; 1625 } 1626 1627 if (value instanceof IPrimitiveType) { 1628 IPrimitiveType<?> nextValue = (IPrimitiveType<?>) value; 1629 String systemAsString = null; 1630 String valueAsString = nextValue.getValueAsString(); 1631 if ("CodeSystem.concept.code".equals(path)) { 1632 systemAsString = myUseSystem; 1633 } else if ("ValueSet.codeSystem.concept.code".equals(path)) { 1634 systemAsString = myUseSystem; 1635 } 1636 1637 if (value instanceof IIdType) { 1638 valueAsString = ((IIdType) value).getIdPart(); 1639 } 1640 1641 BaseSearchParamExtractor.this.createTokenIndexIfNotBlankAndAdd(myResourceTypeName, params, searchParam, systemAsString, valueAsString); 1642 return; 1643 } 1644 1645 switch (path) { 1646 case "Patient.communication": 1647 BaseSearchParamExtractor.this.addToken_PatientCommunication(myResourceTypeName, params, searchParam, value); 1648 return; 1649 case "Consent.source": 1650 // Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that 1651 return; 1652 case "Location.position": 1653 BaseSearchParamExtractor.this.addCoords_Position(myResourceTypeName, params, searchParam, value); 1654 return; 1655 case "StructureDefinition.context": 1656 // TODO: implement this 1657 ourLog.warn("StructureDefinition context indexing not currently supported"); 1658 return; 1659 case "CapabilityStatement.rest.security": 1660 BaseSearchParamExtractor.this.addToken_CapabilityStatementRestSecurity(myResourceTypeName, params, searchParam, value); 1661 return; 1662 } 1663 1664 String nextType = BaseSearchParamExtractor.this.toRootTypeName(value); 1665 switch (nextType) { 1666 case "Identifier": 1667 BaseSearchParamExtractor.this.addToken_Identifier(myResourceTypeName, params, searchParam, value); 1668 break; 1669 case "CodeableConcept": 1670 BaseSearchParamExtractor.this.addToken_CodeableConcept(myResourceTypeName, params, searchParam, value); 1671 break; 1672 case "Coding": 1673 BaseSearchParamExtractor.this.addToken_Coding(myResourceTypeName, params, searchParam, value); 1674 break; 1675 case "ContactPoint": 1676 BaseSearchParamExtractor.this.addToken_ContactPoint(myResourceTypeName, params, searchParam, value); 1677 break; 1678 default: 1679 BaseSearchParamExtractor.this.addUnexpectedDatatypeWarning(params, searchParam, value); 1680 break; 1681 } 1682 } 1683 } 1684 1685 private static class CompositeExtractor<T> implements IExtractor<T> { 1686 1687 private final IExtractor<T> myExtractor0; 1688 private final IExtractor<T> myExtractor1; 1689 1690 private CompositeExtractor(IExtractor<T> theExtractor0, IExtractor<T> theExtractor1) { 1691 myExtractor0 = theExtractor0; 1692 myExtractor1 = theExtractor1; 1693 } 1694 1695 @Override 1696 public void extract(SearchParamSet<T> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath, boolean theWantLocalReferences) { 1697 myExtractor0.extract(theParams, theSearchParam, theValue, thePath, theWantLocalReferences); 1698 myExtractor1.extract(theParams, theSearchParam, theValue, thePath, theWantLocalReferences); 1699 } 1700 } 1701 1702}