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