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