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