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