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