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