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