001package ca.uhn.fhir.jpa.searchparam.extractor;
002
003/*
004 * #%L
005 * HAPI FHIR Search Parameters
006 * %%
007 * Copyright (C) 2014 - 2022 Smile CDR, Inc.
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
024import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
025import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
026import ca.uhn.fhir.context.FhirContext;
027import ca.uhn.fhir.context.FhirVersionEnum;
028import ca.uhn.fhir.context.RuntimeResourceDefinition;
029import ca.uhn.fhir.context.RuntimeSearchParam;
030import ca.uhn.fhir.i18n.Msg;
031import ca.uhn.fhir.jpa.model.config.PartitionSettings;
032import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
033import ca.uhn.fhir.jpa.model.entity.ModelConfig;
034import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
035import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
036import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
037import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
038import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
039import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized;
040import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
041import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
042import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
043import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
044import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
045import ca.uhn.fhir.model.primitive.BoundCodeDt;
046import ca.uhn.fhir.rest.api.Constants;
047import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
048import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
049import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
050import ca.uhn.fhir.util.FhirTerser;
051import ca.uhn.fhir.util.HapiExtensions;
052import ca.uhn.fhir.util.StringUtil;
053import com.google.common.annotations.VisibleForTesting;
054import com.google.common.collect.Sets;
055import org.apache.commons.lang3.ObjectUtils;
056import org.apache.commons.lang3.StringUtils;
057import org.apache.commons.lang3.Validate;
058import org.apache.commons.text.StringTokenizer;
059import org.fhir.ucum.Pair;
060import org.hl7.fhir.exceptions.FHIRException;
061import org.hl7.fhir.instance.model.api.IBase;
062import org.hl7.fhir.instance.model.api.IBaseEnumeration;
063import org.hl7.fhir.instance.model.api.IBaseExtension;
064import org.hl7.fhir.instance.model.api.IBaseReference;
065import org.hl7.fhir.instance.model.api.IBaseResource;
066import org.hl7.fhir.instance.model.api.IIdType;
067import org.hl7.fhir.instance.model.api.IPrimitiveType;
068import org.springframework.beans.factory.annotation.Autowired;
069import org.springframework.context.ApplicationContext;
070
071import javax.annotation.Nonnull;
072import javax.annotation.PostConstruct;
073import javax.measure.quantity.Quantity;
074import javax.measure.unit.NonSI;
075import javax.measure.unit.Unit;
076import java.math.BigDecimal;
077import java.util.ArrayList;
078import java.util.Arrays;
079import java.util.Collection;
080import java.util.Collections;
081import java.util.Date;
082import java.util.HashSet;
083import java.util.List;
084import java.util.Objects;
085import java.util.Optional;
086import java.util.Set;
087import java.util.TreeSet;
088import java.util.stream.Collectors;
089
090import static org.apache.commons.lang3.StringUtils.isBlank;
091import static org.apache.commons.lang3.StringUtils.isNotBlank;
092import static org.apache.commons.lang3.StringUtils.trim;
093
094public abstract class BaseSearchParamExtractor implements ISearchParamExtractor {
095
096        public static final Set<String> COORDS_INDEX_PATHS;
097        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseSearchParamExtractor.class);
098
099        static {
100                Set<String> coordsIndexPaths = Sets.newHashSet("Location.position");
101                COORDS_INDEX_PATHS = Collections.unmodifiableSet(coordsIndexPaths);
102        }
103
104        @Autowired
105        protected ApplicationContext myApplicationContext;
106        @Autowired
107        private FhirContext myContext;
108        @Autowired
109        private ISearchParamRegistry mySearchParamRegistry;
110        @Autowired
111        private ModelConfig myModelConfig;
112        @Autowired
113        private PartitionSettings myPartitionSettings;
114        private Set<String> myIgnoredForSearchDatatypes;
115        private BaseRuntimeChildDefinition myQuantityValueValueChild;
116        private BaseRuntimeChildDefinition myQuantitySystemValueChild;
117        private BaseRuntimeChildDefinition myQuantityCodeValueChild;
118        private BaseRuntimeChildDefinition myMoneyValueChild;
119        private BaseRuntimeChildDefinition myMoneyCurrencyChild;
120        private BaseRuntimeElementCompositeDefinition<?> myLocationPositionDefinition;
121        private BaseRuntimeChildDefinition myCodeSystemUrlValueChild;
122        private BaseRuntimeChildDefinition myRangeLowValueChild;
123        private BaseRuntimeChildDefinition myRangeHighValueChild;
124        private BaseRuntimeChildDefinition myAddressLineValueChild;
125        private BaseRuntimeChildDefinition myAddressCityValueChild;
126        private BaseRuntimeChildDefinition myAddressStateValueChild;
127        private BaseRuntimeChildDefinition myAddressCountryValueChild;
128        private BaseRuntimeChildDefinition myAddressPostalCodeValueChild;
129        private BaseRuntimeChildDefinition myCapabilityStatementRestSecurityServiceValueChild;
130        private BaseRuntimeChildDefinition myPeriodStartValueChild;
131        private BaseRuntimeChildDefinition myPeriodEndValueChild;
132        private BaseRuntimeChildDefinition myTimingEventValueChild;
133        private BaseRuntimeChildDefinition myTimingRepeatValueChild;
134        private BaseRuntimeChildDefinition myTimingRepeatBoundsValueChild;
135        private BaseRuntimeChildDefinition myDurationSystemValueChild;
136        private BaseRuntimeChildDefinition myDurationCodeValueChild;
137        private BaseRuntimeChildDefinition myDurationValueValueChild;
138        private BaseRuntimeChildDefinition myHumanNameFamilyValueChild;
139        private BaseRuntimeChildDefinition myHumanNameGivenValueChild;
140        private BaseRuntimeChildDefinition myHumanNameTextValueChild;
141        private BaseRuntimeChildDefinition myHumanNamePrefixValueChild;
142        private BaseRuntimeChildDefinition myHumanNameSuffixValueChild;
143        private BaseRuntimeChildDefinition myContactPointValueValueChild;
144        private BaseRuntimeChildDefinition myIdentifierSystemValueChild;
145        private BaseRuntimeChildDefinition myIdentifierValueValueChild;
146        private BaseRuntimeChildDefinition myIdentifierTypeValueChild;
147        private BaseRuntimeChildDefinition myIdentifierTypeTextValueChild;
148        private BaseRuntimeChildDefinition myCodeableConceptCodingValueChild;
149        private BaseRuntimeChildDefinition myCodeableConceptTextValueChild;
150        private BaseRuntimeChildDefinition myCodingSystemValueChild;
151        private BaseRuntimeChildDefinition myCodingCodeValueChild;
152        private BaseRuntimeChildDefinition myCodingDisplayValueChild;
153        private BaseRuntimeChildDefinition myContactPointSystemValueChild;
154        private BaseRuntimeChildDefinition myPatientCommunicationLanguageValueChild;
155
156        /**
157         * Constructor
158         */
159        BaseSearchParamExtractor() {
160                super();
161        }
162
163        /**
164         * UNIT TEST constructor
165         */
166        BaseSearchParamExtractor(ModelConfig theModelConfig, PartitionSettings thePartitionSettings, FhirContext theCtx, ISearchParamRegistry theSearchParamRegistry) {
167                Validate.notNull(theModelConfig);
168                Validate.notNull(theCtx);
169                Validate.notNull(theSearchParamRegistry);
170
171                myModelConfig = theModelConfig;
172                myContext = theCtx;
173                mySearchParamRegistry = theSearchParamRegistry;
174                myPartitionSettings = thePartitionSettings;
175        }
176
177        @Override
178        public SearchParamSet<PathAndRef> extractResourceLinks(IBaseResource theResource, boolean theWantLocalReferences) {
179                IExtractor<PathAndRef> extractor = createReferenceExtractor();
180                return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.REFERENCE, theWantLocalReferences);
181        }
182
183        private IExtractor<PathAndRef> createReferenceExtractor() {
184                return new ResourceLinkExtractor();
185        }
186
187        @Override
188        public PathAndRef extractReferenceLinkFromResource(IBase theValue, String thePath) {
189                ResourceLinkExtractor extractor = new ResourceLinkExtractor();
190                return extractor.get(theValue, thePath);
191        }
192
193        @Override
194        public List<String> extractParamValuesAsStrings(RuntimeSearchParam theSearchParam, IBaseResource theResource) {
195                IExtractor extractor;
196                switch (theSearchParam.getParamType()) {
197                        case DATE:
198                                extractor = createDateExtractor(theResource);
199                                break;
200                        case STRING:
201                                extractor = createStringExtractor(theResource);
202                                break;
203                        case TOKEN:
204                                extractor = createTokenExtractor(theResource);
205                                break;
206                        case NUMBER:
207                                extractor = createNumberExtractor(theResource);
208                                break;
209                        case REFERENCE:
210                                extractor = createReferenceExtractor();
211                                return extractReferenceParamsAsQueryTokens(theSearchParam, theResource, extractor);
212                        case QUANTITY:
213                                if (myModelConfig.getNormalizedQuantitySearchLevel().equals(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED)) {
214                                        extractor = new CompositeExtractor(
215                                                createQuantityExtractor(theResource),
216                                                createQuantityNormalizedExtractor(theResource)
217                                        );
218                                } else {
219                                        extractor = createQuantityExtractor(theResource);
220                                }
221                                break;
222                        case URI:
223                                extractor = createUriExtractor(theResource);
224                                break;
225                        case SPECIAL:
226                                extractor = createSpecialExtractor(theResource.getIdElement().getResourceType());
227                                break;
228                        case COMPOSITE:
229                        default:
230                                throw new UnsupportedOperationException(Msg.code(503) + "Type " + theSearchParam.getParamType() + " not supported for extraction");
231                }
232
233                return extractParamsAsQueryTokens(theSearchParam, theResource, extractor);
234        }
235
236        private List<String> extractReferenceParamsAsQueryTokens(RuntimeSearchParam theSearchParam, IBaseResource theResource, IExtractor<PathAndRef> theExtractor) {
237                SearchParamSet<PathAndRef> params = new SearchParamSet<>();
238                extractSearchParam(theSearchParam, theResource, theExtractor, params, false);
239                return refsToStringList(params);
240        }
241
242        private List<String> refsToStringList(SearchParamSet<PathAndRef> theParams) {
243                return theParams.stream()
244                        .map(PathAndRef::getRef)
245                        .map(ref -> ref.getReferenceElement().toUnqualifiedVersionless().getValue())
246                        .collect(Collectors.toList());
247        }
248
249        private <T extends BaseResourceIndexedSearchParam> List<String> extractParamsAsQueryTokens(RuntimeSearchParam theSearchParam, IBaseResource theResource, IExtractor<T> theExtractor) {
250                SearchParamSet<T> params = new SearchParamSet<>();
251                extractSearchParam(theSearchParam, theResource, theExtractor, params, false);
252                return toStringList(params);
253        }
254
255        private <T extends BaseResourceIndexedSearchParam> List<String> toStringList(SearchParamSet<T> theParams) {
256                return theParams.stream()
257                        .map(param -> param.toQueryParameterType().getValueAsQueryToken(myContext))
258                        .collect(Collectors.toList());
259        }
260
261        @Override
262        public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource) {
263                IExtractor<BaseResourceIndexedSearchParam> extractor = createTokenExtractor(theResource);
264                return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.TOKEN, false);
265        }
266
267        @Override
268        public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource, RuntimeSearchParam theSearchParam) {
269                IExtractor<BaseResourceIndexedSearchParam> extractor = createTokenExtractor(theResource);
270                SearchParamSet<BaseResourceIndexedSearchParam> setToPopulate = new SearchParamSet<>();
271                extractSearchParam(theSearchParam, theResource, extractor, setToPopulate, false);
272                return setToPopulate;
273        }
274
275        private IExtractor<BaseResourceIndexedSearchParam> createTokenExtractor(IBaseResource theResource) {
276                String resourceTypeName = toRootTypeName(theResource);
277                String useSystem;
278                if (getContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2)) {
279                        if (resourceTypeName.equals("ValueSet")) {
280                                ca.uhn.fhir.model.dstu2.resource.ValueSet dstu2ValueSet = (ca.uhn.fhir.model.dstu2.resource.ValueSet) theResource;
281                                useSystem = dstu2ValueSet.getCodeSystem().getSystem();
282                        } else {
283                                useSystem = null;
284                        }
285                } else {
286                        if (resourceTypeName.equals("CodeSystem")) {
287                                useSystem = extractValueAsString(myCodeSystemUrlValueChild, theResource);
288                        } else {
289                                useSystem = null;
290                        }
291                }
292
293                return new TokenExtractor(resourceTypeName, useSystem);
294        }
295
296        @Override
297        public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamSpecial(IBaseResource theResource) {
298                String resourceTypeName = toRootTypeName(theResource);
299                IExtractor<BaseResourceIndexedSearchParam> extractor = createSpecialExtractor(resourceTypeName);
300                return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.SPECIAL, false);
301        }
302
303        private IExtractor<BaseResourceIndexedSearchParam> createSpecialExtractor(String theResourceTypeName) {
304                return (params, searchParam, value, path, theWantLocalReferences) -> {
305                        if (COORDS_INDEX_PATHS.contains(path)) {
306                                addCoords_Position(theResourceTypeName, params, searchParam, value);
307                        }
308                };
309        }
310
311        private void addUnexpectedDatatypeWarning(SearchParamSet<?> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
312                theParams.addWarning("Search param " + theSearchParam.getName() + " is of unexpected datatype: " + theValue.getClass());
313        }
314
315        @Override
316        public SearchParamSet<ResourceIndexedSearchParamUri> extractSearchParamUri(IBaseResource theResource) {
317                IExtractor<ResourceIndexedSearchParamUri> extractor = createUriExtractor(theResource);
318                return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.URI, false);
319        }
320
321        private IExtractor<ResourceIndexedSearchParamUri> createUriExtractor(IBaseResource theResource) {
322                return (params, searchParam, value, path, theWantLocalReferences) -> {
323                        String nextType = toRootTypeName(value);
324                        String resourceType = toRootTypeName(theResource);
325                        switch (nextType) {
326                                case "uri":
327                                case "url":
328                                case "oid":
329                                case "sid":
330                                case "uuid":
331                                        addUri_Uri(resourceType, params, searchParam, value);
332                                        break;
333                                default:
334                                        addUnexpectedDatatypeWarning(params, searchParam, value);
335                                        break;
336                        }
337                };
338        }
339
340        @Override
341        public SearchParamSet<ResourceIndexedSearchParamDate> extractSearchParamDates(IBaseResource theResource) {
342                IExtractor<ResourceIndexedSearchParamDate> extractor = createDateExtractor(theResource);
343                return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.DATE, false);
344        }
345
346        private IExtractor<ResourceIndexedSearchParamDate> createDateExtractor(IBaseResource theResource) {
347                return new DateExtractor(theResource);
348        }
349
350        @Override
351        public Date extractDateFromResource(IBase theValue, String thePath) {
352                DateExtractor extractor = new DateExtractor("DateType");
353                return extractor.get(theValue, thePath, false).getValueHigh();
354        }
355
356        @Override
357        public SearchParamSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(IBaseResource theResource) {
358                IExtractor<ResourceIndexedSearchParamNumber> extractor = createNumberExtractor(theResource);
359                return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.NUMBER, false);
360        }
361
362        private IExtractor<ResourceIndexedSearchParamNumber> createNumberExtractor(IBaseResource theResource) {
363                return (params, searchParam, value, path, theWantLocalReferences) -> {
364                        String nextType = toRootTypeName(value);
365                        String resourceType = toRootTypeName(theResource);
366                        switch (nextType) {
367                                case "Duration":
368                                        addNumber_Duration(resourceType, params, searchParam, value);
369                                        break;
370                                case "Quantity":
371                                        addNumber_Quantity(resourceType, params, searchParam, value);
372                                        break;
373                                case "integer":
374                                case "positiveInt":
375                                case "unsignedInt":
376                                        addNumber_Integer(resourceType, params, searchParam, value);
377                                        break;
378                                case "decimal":
379                                        addNumber_Decimal(resourceType, params, searchParam, value);
380                                        break;
381                                default:
382                                        addUnexpectedDatatypeWarning(params, searchParam, value);
383                                        break;
384                        }
385                };
386        }
387
388        @Override
389        public SearchParamSet<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(IBaseResource theResource) {
390                IExtractor<ResourceIndexedSearchParamQuantity> extractor = createQuantityExtractor(theResource);
391                return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY, false);
392        }
393
394
395        @Override
396        public SearchParamSet<ResourceIndexedSearchParamQuantityNormalized> extractSearchParamQuantityNormalized(IBaseResource theResource) {
397                IExtractor<ResourceIndexedSearchParamQuantityNormalized> extractor = createQuantityNormalizedExtractor(theResource);
398                return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY, false);
399        }
400
401        private IExtractor<ResourceIndexedSearchParamQuantity> createQuantityExtractor(IBaseResource theResource) {
402                return (params, searchParam, value, path, theWantLocalReferences) -> {
403                        if (value.getClass().equals(myLocationPositionDefinition.getImplementingClass())) {
404                                return;
405                        }
406
407                        String nextType = toRootTypeName(value);
408                        String resourceType = toRootTypeName(theResource);
409                        switch (nextType) {
410                                case "Quantity":
411                                        addQuantity_Quantity(resourceType, params, searchParam, value);
412                                        break;
413                                case "Money":
414                                        addQuantity_Money(resourceType, params, searchParam, value);
415                                        break;
416                                case "Range":
417                                        addQuantity_Range(resourceType, params, searchParam, value);
418                                        break;
419                                default:
420                                        addUnexpectedDatatypeWarning(params, searchParam, value);
421                                        break;
422                        }
423                };
424        }
425
426        private IExtractor<ResourceIndexedSearchParamQuantityNormalized> createQuantityNormalizedExtractor(IBaseResource theResource) {
427
428                return (params, searchParam, value, path, theWantLocalReferences) -> {
429                        if (value.getClass().equals(myLocationPositionDefinition.getImplementingClass())) {
430                                return;
431                        }
432
433                        String nextType = toRootTypeName(value);
434                        String resourceType = toRootTypeName(theResource);
435                        switch (nextType) {
436                                case "Quantity":
437                                        addQuantity_QuantityNormalized(resourceType, params, searchParam, value);
438                                        break;
439                                case "Money":
440                                        addQuantity_MoneyNormalized(resourceType, params, searchParam, value);
441                                        break;
442                                case "Range":
443                                        addQuantity_RangeNormalized(resourceType, params, searchParam, value);
444                                        break;
445                                default:
446                                        addUnexpectedDatatypeWarning(params, searchParam, value);
447                                        break;
448                        }
449                };
450        }
451
452        @Override
453        public SearchParamSet<ResourceIndexedSearchParamString> extractSearchParamStrings(IBaseResource theResource) {
454                IExtractor<ResourceIndexedSearchParamString> extractor = createStringExtractor(theResource);
455
456                return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.STRING, false);
457        }
458
459        private IExtractor<ResourceIndexedSearchParamString> createStringExtractor(IBaseResource theResource) {
460                return (params, searchParam, value, path, theWantLocalReferences) -> {
461                        String resourceType = toRootTypeName(theResource);
462
463                        if (value instanceof IPrimitiveType) {
464                                IPrimitiveType<?> nextValue = (IPrimitiveType<?>) value;
465                                String valueAsString = nextValue.getValueAsString();
466                                createStringIndexIfNotBlank(resourceType, params, searchParam, valueAsString);
467                                return;
468                        }
469
470                        String nextType = toRootTypeName(value);
471                        switch (nextType) {
472                                case "HumanName":
473                                        addString_HumanName(resourceType, params, searchParam, value);
474                                        break;
475                                case "Address":
476                                        addString_Address(resourceType, params, searchParam, value);
477                                        break;
478                                case "ContactPoint":
479                                        addString_ContactPoint(resourceType, params, searchParam, value);
480                                        break;
481                                case "Quantity":
482                                        addString_Quantity(resourceType, params, searchParam, value);
483                                        break;
484                                case "Range":
485                                        addString_Range(resourceType, params, searchParam, value);
486                                        break;
487                                default:
488                                        addUnexpectedDatatypeWarning(params, searchParam, value);
489                                        break;
490                        }
491                };
492        }
493
494        /**
495         * Override parent because we're using FHIRPath here
496         */
497        @Override
498        public List<IBase> extractValues(String thePaths, IBaseResource theResource) {
499                List<IBase> values = new ArrayList<>();
500                if (isNotBlank(thePaths)) {
501                        String[] nextPathsSplit = split(thePaths);
502                        for (String nextPath : nextPathsSplit) {
503                                List<? extends IBase> allValues;
504
505                                // This path is hard to parse and isn't likely to produce anything useful anyway
506                                if (myContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU2)) {
507                                        if (nextPath.equals("Bundle.entry.resource(0)")) {
508                                                continue;
509                                        }
510                                }
511
512                                nextPath = trim(nextPath);
513                                IValueExtractor allValuesFunc = getPathValueExtractor(theResource, nextPath);
514                                try {
515                                        allValues = allValuesFunc.get();
516                                } catch (Exception e) {
517                                        String msg = getContext().getLocalizer().getMessage(BaseSearchParamExtractor.class, "failedToExtractPaths", nextPath, e.toString());
518                                        throw new InternalErrorException(Msg.code(504) + msg, e);
519                                }
520
521                                values.addAll(allValues);
522                        }
523
524                        for (int i = 0; i < values.size(); i++) {
525                                IBase nextObject = values.get(i);
526                                if (nextObject instanceof IBaseExtension) {
527                                        IBaseExtension nextExtension = (IBaseExtension) nextObject;
528                                        nextObject = nextExtension.getValue();
529                                        values.set(i, nextObject);
530                                }
531                        }
532                }
533
534                return values;
535        }
536
537        protected FhirContext getContext() {
538                return myContext;
539        }
540
541        @VisibleForTesting
542        public void setContext(FhirContext theContext) {
543                myContext = theContext;
544        }
545
546        protected ModelConfig getModelConfig() {
547                return myModelConfig;
548        }
549
550        @VisibleForTesting
551        public void setModelConfig(ModelConfig theModelConfig) {
552                myModelConfig = theModelConfig;
553        }
554
555        @VisibleForTesting
556        public void setSearchParamRegistry(ISearchParamRegistry theSearchParamRegistry) {
557                mySearchParamRegistry = theSearchParamRegistry;
558        }
559
560        private Collection<RuntimeSearchParam> getSearchParams(IBaseResource theResource) {
561                RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource);
562                Collection<RuntimeSearchParam> retVal = mySearchParamRegistry.getActiveSearchParams(def.getName()).values();
563                List<RuntimeSearchParam> defaultList = Collections.emptyList();
564                retVal = ObjectUtils.defaultIfNull(retVal, defaultList);
565                return retVal;
566        }
567
568        private void addQuantity_Quantity(String theResourceType, Set<ResourceIndexedSearchParamQuantity> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
569                Optional<IPrimitiveType<BigDecimal>> valueField = myQuantityValueValueChild.getAccessor().getFirstValueOrNull(theValue);
570                if (valueField.isPresent() && valueField.get().getValue() != null) {
571                        BigDecimal nextValueValue = valueField.get().getValue();
572                        String system = extractValueAsString(myQuantitySystemValueChild, theValue);
573                        String code = extractValueAsString(myQuantityCodeValueChild, theValue);
574
575                        ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(myPartitionSettings, theResourceType, theSearchParam.getName(), nextValueValue, system, code);
576
577                        theParams.add(nextEntity);
578                }
579        }
580
581        private void addQuantity_QuantityNormalized(String theResourceType, Set<ResourceIndexedSearchParamQuantityNormalized> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
582                Optional<IPrimitiveType<BigDecimal>> valueField = myQuantityValueValueChild.getAccessor().getFirstValueOrNull(theValue);
583                if (valueField.isPresent() && valueField.get().getValue() != null) {
584                        BigDecimal nextValueValue = valueField.get().getValue();
585                        String system = extractValueAsString(myQuantitySystemValueChild, theValue);
586                        String code = extractValueAsString(myQuantityCodeValueChild, theValue);
587
588                        //-- convert the value/unit to the canonical form if any
589                        Pair canonicalForm = UcumServiceUtil.getCanonicalForm(system, nextValueValue, code);
590                        if (canonicalForm != null) {
591                                double canonicalValue = Double.parseDouble(canonicalForm.getValue().asDecimal());
592                                String canonicalUnits = canonicalForm.getCode();
593                                ResourceIndexedSearchParamQuantityNormalized nextEntity = new ResourceIndexedSearchParamQuantityNormalized(myPartitionSettings, theResourceType, theSearchParam.getName(), canonicalValue, system, canonicalUnits);
594                                theParams.add(nextEntity);
595                        }
596
597                }
598        }
599
600        private void addQuantity_Money(String theResourceType, Set<ResourceIndexedSearchParamQuantity> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
601                Optional<IPrimitiveType<BigDecimal>> valueField = myMoneyValueChild.getAccessor().getFirstValueOrNull(theValue);
602                if (valueField.isPresent() && valueField.get().getValue() != null) {
603                        BigDecimal nextValueValue = valueField.get().getValue();
604
605                        String nextValueString = "urn:iso:std:iso:4217";
606                        String nextValueCode = extractValueAsString(myMoneyCurrencyChild, theValue);
607                        String searchParamName = theSearchParam.getName();
608
609                        ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(myPartitionSettings, theResourceType, searchParamName, nextValueValue, nextValueString, nextValueCode);
610                        theParams.add(nextEntity);
611                }
612        }
613
614        private void addQuantity_MoneyNormalized(String theResourceType, Set<ResourceIndexedSearchParamQuantityNormalized> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
615                Optional<IPrimitiveType<BigDecimal>> valueField = myMoneyValueChild.getAccessor().getFirstValueOrNull(theValue);
616                if (valueField.isPresent() && valueField.get().getValue() != null) {
617                        BigDecimal nextValueValue = valueField.get().getValue();
618
619                        String nextValueString = "urn:iso:std:iso:4217";
620                        String nextValueCode = extractValueAsString(myMoneyCurrencyChild, theValue);
621                        String searchParamName = theSearchParam.getName();
622
623                        ResourceIndexedSearchParamQuantityNormalized nextEntityNormalized = new ResourceIndexedSearchParamQuantityNormalized(myPartitionSettings, theResourceType, searchParamName, nextValueValue.doubleValue(), nextValueString, nextValueCode);
624                        theParams.add(nextEntityNormalized);
625                }
626        }
627
628        private void addQuantity_Range(String theResourceType, Set<ResourceIndexedSearchParamQuantity> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
629                Optional<IBase> low = myRangeLowValueChild.getAccessor().getFirstValueOrNull(theValue);
630                low.ifPresent(theIBase -> addQuantity_Quantity(theResourceType, theParams, theSearchParam, theIBase));
631
632                Optional<IBase> high = myRangeHighValueChild.getAccessor().getFirstValueOrNull(theValue);
633                high.ifPresent(theIBase -> addQuantity_Quantity(theResourceType, theParams, theSearchParam, theIBase));
634        }
635
636        private void addQuantity_RangeNormalized(String theResourceType, Set<ResourceIndexedSearchParamQuantityNormalized> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
637                Optional<IBase> low = myRangeLowValueChild.getAccessor().getFirstValueOrNull(theValue);
638                low.ifPresent(theIBase -> addQuantity_QuantityNormalized(theResourceType, theParams, theSearchParam, theIBase));
639
640                Optional<IBase> high = myRangeHighValueChild.getAccessor().getFirstValueOrNull(theValue);
641                high.ifPresent(theIBase -> addQuantity_QuantityNormalized(theResourceType, theParams, theSearchParam, theIBase));
642        }
643
644        @SuppressWarnings("unchecked")
645        private void addToken_Identifier(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
646                String system = extractValueAsString(myIdentifierSystemValueChild, theValue);
647                String value = extractValueAsString(myIdentifierValueValueChild, theValue);
648                if (isNotBlank(value)) {
649                        createTokenIndexIfNotBlankAndAdd(theResourceType, theParams, theSearchParam, system, value);
650
651                        boolean indexIdentifierType = myModelConfig.isIndexIdentifierOfType();
652                        if (indexIdentifierType) {
653                                Optional<IBase> type = myIdentifierTypeValueChild.getAccessor().getFirstValueOrNull(theValue);
654                                if (type.isPresent()) {
655                                        List<IBase> codings = myCodeableConceptCodingValueChild.getAccessor().getValues(type.get());
656                                        for (IBase nextCoding : codings) {
657
658                                                String typeSystem = myCodingSystemValueChild.getAccessor().getFirstValueOrNull(nextCoding).map(t -> ((IPrimitiveType<String>) t).getValue()).orElse(null);
659                                                String typeValue = myCodingCodeValueChild.getAccessor().getFirstValueOrNull(nextCoding).map(t -> ((IPrimitiveType<String>) t).getValue()).orElse(null);
660                                                if (isNotBlank(typeSystem) && isNotBlank(typeValue)) {
661                                                        String paramName = theSearchParam.getName() + Constants.PARAMQUALIFIER_TOKEN_OF_TYPE;
662                                                        ResourceIndexedSearchParamToken token = createTokenIndexIfNotBlank(theResourceType, typeSystem, typeValue + "|" + value, paramName);
663                                                        if (token != null) {
664                                                                theParams.add(token);
665                                                        }
666                                                }
667
668                                        }
669                                }
670                        }
671                }
672
673        }
674
675        protected boolean shouldIndexTextComponentOfToken(RuntimeSearchParam theSearchParam) {
676                return tokenTextIndexingEnabledForSearchParam(myModelConfig, theSearchParam);
677        }
678
679        private void addToken_CodeableConcept(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
680                List<IBase> codings = getCodingsFromCodeableConcept(theValue);
681                for (IBase nextCoding : codings) {
682                        addToken_Coding(theResourceType, theParams, theSearchParam, nextCoding);
683                }
684
685                if (shouldIndexTextComponentOfToken(theSearchParam)) {
686                        String text = getDisplayTextFromCodeableConcept(theValue);
687                        if (isNotBlank(text)) {
688                                createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, text);
689                        }
690                }
691        }
692
693        @Override
694        public List<IBase> getCodingsFromCodeableConcept(IBase theValue) {
695                String nextType = BaseSearchParamExtractor.this.toRootTypeName(theValue);
696                if ("CodeableConcept".equals(nextType)) {
697                        return myCodeableConceptCodingValueChild.getAccessor().getValues(theValue);
698                } else {
699                        return null;
700                }
701        }
702
703        @Override
704        public String getDisplayTextFromCodeableConcept(IBase theValue) {
705                String nextType = BaseSearchParamExtractor.this.toRootTypeName(theValue);
706                if ("CodeableConcept".equals(nextType)) {
707                        return extractValueAsString(myCodeableConceptTextValueChild, theValue);
708                } else {
709                        return null;
710                }
711        }
712
713        private void addToken_Coding(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
714                ResourceIndexedSearchParamToken resourceIndexedSearchParamToken = createSearchParamForCoding(theResourceType, theSearchParam, theValue);
715                if (resourceIndexedSearchParamToken != null) {
716                        theParams.add(resourceIndexedSearchParamToken);
717                }
718
719                if (shouldIndexTextComponentOfToken(theSearchParam)) {
720                        String text = getDisplayTextForCoding(theValue);
721                        createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, text);
722                }
723        }
724
725        @Override
726        public ResourceIndexedSearchParamToken createSearchParamForCoding(String theResourceType, RuntimeSearchParam theSearchParam, IBase theValue) {
727                String nextType = BaseSearchParamExtractor.this.toRootTypeName(theValue);
728                if ("Coding".equals(nextType)) {
729                        String system = extractValueAsString(myCodingSystemValueChild, theValue);
730                        String code = extractValueAsString(myCodingCodeValueChild, theValue);
731                        return createTokenIndexIfNotBlank(theResourceType, system, code, theSearchParam.getName());
732                } else {
733                        return null;
734                }
735        }
736
737        @Override
738        public String getDisplayTextForCoding(IBase theValue) {
739                String nextType = BaseSearchParamExtractor.this.toRootTypeName(theValue);
740                if ("Coding".equals(nextType)) {
741                        return extractValueAsString(myCodingDisplayValueChild, theValue);
742                } else {
743                        return null;
744                }
745        }
746
747        private void addToken_ContactPoint(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
748                String system = extractValueAsString(myContactPointSystemValueChild, theValue);
749                String value = extractValueAsString(myContactPointValueValueChild, theValue);
750                createTokenIndexIfNotBlankAndAdd(theResourceType, theParams, theSearchParam, system, value);
751        }
752
753        private void addToken_PatientCommunication(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
754                List<IBase> values = myPatientCommunicationLanguageValueChild.getAccessor().getValues(theValue);
755                for (IBase next : values) {
756                        addToken_CodeableConcept(theResourceType, theParams, theSearchParam, next);
757                }
758        }
759
760        private void addToken_CapabilityStatementRestSecurity(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
761                List<IBase> values = myCapabilityStatementRestSecurityServiceValueChild.getAccessor().getValues(theValue);
762                for (IBase nextValue : values) {
763                        addToken_CodeableConcept(theResourceType, theParams, theSearchParam, nextValue);
764                }
765        }
766
767        private void addDate_Period(String theResourceType, Set<ResourceIndexedSearchParamDate> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
768                Date start = extractValueAsDate(myPeriodStartValueChild, theValue);
769                String startAsString = extractValueAsString(myPeriodStartValueChild, theValue);
770                Date end = extractValueAsDate(myPeriodEndValueChild, theValue);
771                String endAsString = extractValueAsString(myPeriodEndValueChild, theValue);
772
773                if (start != null || end != null) {
774
775                        if (start == null) {
776                                start = myModelConfig.getPeriodIndexStartOfTime().getValue();
777                                startAsString = myModelConfig.getPeriodIndexStartOfTime().getValueAsString();
778                        }
779                        if (end == null) {
780                                end = myModelConfig.getPeriodIndexEndOfTime().getValue();
781                                endAsString = myModelConfig.getPeriodIndexEndOfTime().getValueAsString();
782                        }
783
784                        ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(myPartitionSettings, theResourceType, theSearchParam.getName(), start, startAsString, end, endAsString, startAsString);
785                        theParams.add(nextEntity);
786                }
787        }
788
789        private void addDate_Timing(String theResourceType, Set<ResourceIndexedSearchParamDate> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
790                List<IPrimitiveType<Date>> values = extractValuesAsFhirDates(myTimingEventValueChild, theValue);
791
792                TreeSet<Date> dates = new TreeSet<>();
793                TreeSet<String> dateStrings = new TreeSet<>();
794                String firstValue = null;
795                String finalValue = null;
796                for (IPrimitiveType<Date> nextEvent : values) {
797                        if (nextEvent.getValue() != null) {
798                                dates.add(nextEvent.getValue());
799                                if (firstValue == null) {
800                                        firstValue = nextEvent.getValueAsString();
801                                }
802                                finalValue = nextEvent.getValueAsString();
803                        }
804                }
805
806                Optional<IBase> repeat = myTimingRepeatValueChild.getAccessor().getFirstValueOrNull(theValue);
807                if (repeat.isPresent()) {
808                        Optional<IBase> bounds = myTimingRepeatBoundsValueChild.getAccessor().getFirstValueOrNull(repeat.get());
809                        if (bounds.isPresent()) {
810                                String boundsType = toRootTypeName(bounds.get());
811                                if ("Period".equals(boundsType)) {
812                                        Date start = extractValueAsDate(myPeriodStartValueChild, bounds.get());
813                                        Date end = extractValueAsDate(myPeriodEndValueChild, bounds.get());
814                                        String endString = extractValueAsString(myPeriodEndValueChild, bounds.get());
815                                        dates.add(start);
816                                        dates.add(end);
817                                        //TODO Check if this logic is valid. Does the start of the first period indicate a lower bound??
818                                        if (firstValue == null) {
819                                                firstValue = extractValueAsString(myPeriodStartValueChild, bounds.get());
820                                        }
821                                        finalValue = endString;
822                                }
823                        }
824                }
825
826                if (!dates.isEmpty()) {
827                        ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(myPartitionSettings, theResourceType, theSearchParam.getName(), dates.first(), firstValue, dates.last(), finalValue, firstValue);
828                        theParams.add(nextEntity);
829                }
830        }
831
832        private void addNumber_Duration(String theResourceType, Set<ResourceIndexedSearchParamNumber> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
833                String system = extractValueAsString(myDurationSystemValueChild, theValue);
834                String code = extractValueAsString(myDurationCodeValueChild, theValue);
835                BigDecimal value = extractValueAsBigDecimal(myDurationValueValueChild, theValue);
836                if (value != null) {
837                        value = normalizeQuantityContainingTimeUnitsIntoDaysForNumberParam(system, code, value);
838                        ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(myPartitionSettings, theResourceType, theSearchParam.getName(), value);
839                        theParams.add(nextEntity);
840                }
841        }
842
843        private void addNumber_Quantity(String theResourceType, Set<ResourceIndexedSearchParamNumber> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
844                BigDecimal value = extractValueAsBigDecimal(myQuantityValueValueChild, theValue);
845                if (value != null) {
846                        String system = extractValueAsString(myQuantitySystemValueChild, theValue);
847                        String code = extractValueAsString(myQuantityCodeValueChild, theValue);
848                        value = normalizeQuantityContainingTimeUnitsIntoDaysForNumberParam(system, code, value);
849                        ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(myPartitionSettings, theResourceType, theSearchParam.getName(), value);
850                        theParams.add(nextEntity);
851                }
852        }
853
854        @SuppressWarnings("unchecked")
855        private void addNumber_Integer(String theResourceType, Set<ResourceIndexedSearchParamNumber> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
856                IPrimitiveType<Integer> value = (IPrimitiveType<Integer>) theValue;
857                if (value.getValue() != null) {
858                        BigDecimal valueDecimal = new BigDecimal(value.getValue());
859                        ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(myPartitionSettings, theResourceType, theSearchParam.getName(), valueDecimal);
860                        theParams.add(nextEntity);
861                }
862
863        }
864
865        @SuppressWarnings("unchecked")
866        private void addNumber_Decimal(String theResourceType, Set<ResourceIndexedSearchParamNumber> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
867                IPrimitiveType<BigDecimal> value = (IPrimitiveType<BigDecimal>) theValue;
868                if (value.getValue() != null) {
869                        BigDecimal valueDecimal = value.getValue();
870                        ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(myPartitionSettings, theResourceType, theSearchParam.getName(), valueDecimal);
871                        theParams.add(nextEntity);
872                }
873
874        }
875
876        private void addCoords_Position(String theResourceType, SearchParamSet<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
877                BigDecimal latitude = null;
878                BigDecimal longitude = null;
879
880                if (theValue instanceof org.hl7.fhir.dstu3.model.Location.LocationPositionComponent) {
881                        org.hl7.fhir.dstu3.model.Location.LocationPositionComponent value = (org.hl7.fhir.dstu3.model.Location.LocationPositionComponent) theValue;
882                        latitude = value.getLatitude();
883                        longitude = value.getLongitude();
884                } else if (theValue instanceof org.hl7.fhir.r4.model.Location.LocationPositionComponent) {
885                        org.hl7.fhir.r4.model.Location.LocationPositionComponent value = (org.hl7.fhir.r4.model.Location.LocationPositionComponent) theValue;
886                        latitude = value.getLatitude();
887                        longitude = value.getLongitude();
888                } else if (theValue instanceof org.hl7.fhir.r5.model.Location.LocationPositionComponent) {
889                        org.hl7.fhir.r5.model.Location.LocationPositionComponent value = (org.hl7.fhir.r5.model.Location.LocationPositionComponent) theValue;
890                        latitude = value.getLatitude();
891                        longitude = value.getLongitude();
892                }
893                // We only accept coordinates when both are present
894                if (latitude != null && longitude != null) {
895                        double normalizedLatitude = GeopointNormalizer.normalizeLatitude(latitude.doubleValue());
896                        double normalizedLongitude = GeopointNormalizer.normalizeLongitude(longitude.doubleValue());
897                        ResourceIndexedSearchParamCoords nextEntity = new ResourceIndexedSearchParamCoords(myPartitionSettings, theResourceType, theSearchParam.getName(), normalizedLatitude, normalizedLongitude);
898                        theParams.add(nextEntity);
899                }
900        }
901
902        private void addString_HumanName(String theResourceType, Set<ResourceIndexedSearchParamString> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
903                List<BaseRuntimeChildDefinition> myHumanNameChildren = Arrays.asList(myHumanNameFamilyValueChild, myHumanNameGivenValueChild, myHumanNameTextValueChild, myHumanNamePrefixValueChild, myHumanNameSuffixValueChild);
904                for (BaseRuntimeChildDefinition theChild : myHumanNameChildren) {
905                        List<String> indices = extractValuesAsStrings(theChild, theValue);
906                        for (String next : indices) {
907                                createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, next);
908                        }
909                }
910        }
911
912        private void addString_Quantity(String theResourceType, Set<ResourceIndexedSearchParamString> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
913                BigDecimal value = extractValueAsBigDecimal(myQuantityValueValueChild, theValue);
914                if (value != null) {
915                        createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, value.toPlainString());
916                }
917        }
918
919        private void addString_Range(String theResourceType, Set<ResourceIndexedSearchParamString> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
920
921                BigDecimal value = extractValueAsBigDecimal(myRangeLowValueChild, theValue);
922                if (value != null) {
923                        createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, value.toPlainString());
924                }
925        }
926
927        private void addString_ContactPoint(String theResourceType, Set<ResourceIndexedSearchParamString> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
928
929                String value = extractValueAsString(myContactPointValueValueChild, theValue);
930                if (isNotBlank(value)) {
931                        createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, value);
932                }
933        }
934
935        private void addString_Address(String theResourceType, Set<ResourceIndexedSearchParamString> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
936
937                List<String> allNames = new ArrayList<>(extractValuesAsStrings(myAddressLineValueChild, theValue));
938
939                String city = extractValueAsString(myAddressCityValueChild, theValue);
940                if (isNotBlank(city)) {
941                        allNames.add(city);
942                }
943
944                String state = extractValueAsString(myAddressStateValueChild, theValue);
945                if (isNotBlank(state)) {
946                        allNames.add(state);
947                }
948
949                String country = extractValueAsString(myAddressCountryValueChild, theValue);
950                if (isNotBlank(country)) {
951                        allNames.add(country);
952                }
953
954                String postalCode = extractValueAsString(myAddressPostalCodeValueChild, theValue);
955                if (isNotBlank(postalCode)) {
956                        allNames.add(postalCode);
957                }
958
959                for (String nextName : allNames) {
960                        createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, nextName);
961                }
962
963        }
964
965        private <T> SearchParamSet<T> extractSearchParams(IBaseResource theResource, IExtractor<T> theExtractor, RestSearchParameterTypeEnum theSearchParamType, boolean theWantLocalReferences) {
966                SearchParamSet<T> retVal = new SearchParamSet<>();
967
968                Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
969
970                cleanUpContainedResourceReferences(theResource, theSearchParamType, searchParams);
971
972                for (RuntimeSearchParam nextSpDef : searchParams) {
973                        if (nextSpDef.getParamType() != theSearchParamType) {
974                                continue;
975                        }
976
977                        extractSearchParam(nextSpDef, theResource, theExtractor, retVal, theWantLocalReferences);
978                }
979                return retVal;
980        }
981
982
983        /**
984         * Helper function to determine if a set of SPs for a resource uses a resolve as part of its fhir path.
985         */
986        private boolean anySearchParameterUsesResolve(Collection<RuntimeSearchParam> searchParams, RestSearchParameterTypeEnum theSearchParamType) {
987                return searchParams.stream()
988                        .filter(param -> param.getParamType() != theSearchParamType)
989                        .map(RuntimeSearchParam::getPath)
990                        .filter(Objects::nonNull)
991                        .anyMatch(path -> path.contains("resolve"));
992        }
993
994        /**
995         * HAPI FHIR Reference objects (e.g. {@link org.hl7.fhir.r4.model.Reference}) can hold references either by text
996         * (e.g. "#3") or by resource (e.g. "new Reference(patientInstance)"). The FHIRPath evaluator only understands the
997         * first way, so if there is any chance of the FHIRPath evaluator needing to descend across references, we
998         * have to assign values to those references before indexing.
999         * <p>
1000         * Doing this cleanup isn't hugely expensive, but it's not completely free either so we only do it
1001         * if we think there's actually a chance
1002         */
1003        private void cleanUpContainedResourceReferences(IBaseResource theResource, RestSearchParameterTypeEnum theSearchParamType, Collection<RuntimeSearchParam> searchParams) {
1004                boolean havePathWithResolveExpression =
1005                        myModelConfig.isIndexOnContainedResources()
1006                                || anySearchParameterUsesResolve(searchParams, theSearchParamType);
1007
1008                if (havePathWithResolveExpression && myContext.getParserOptions().isAutoContainReferenceTargetsWithNoId()) {
1009                        //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
1010                        myContext.newTerser().containResources(theResource, FhirTerser.OptionsEnum.MODIFY_RESOURCE, FhirTerser.OptionsEnum.STORE_AND_REUSE_RESULTS);
1011                }
1012        }
1013
1014        private <T> void extractSearchParam(RuntimeSearchParam theSearchParameterDef, IBaseResource theResource, IExtractor<T> theExtractor, SearchParamSet<T> theSetToPopulate, boolean theWantLocalReferences) {
1015                String nextPathUnsplit = theSearchParameterDef.getPath();
1016                if (isBlank(nextPathUnsplit)) {
1017                        return;
1018                }
1019
1020                String[] splitPaths = split(nextPathUnsplit);
1021                for (String nextPath : splitPaths) {
1022                        nextPath = trim(nextPath);
1023                        for (IBase nextObject : extractValues(nextPath, theResource)) {
1024                                if (nextObject != null) {
1025                                        String typeName = toRootTypeName(nextObject);
1026                                        if (!myIgnoredForSearchDatatypes.contains(typeName)) {
1027                                                theExtractor.extract(theSetToPopulate, theSearchParameterDef, nextObject, nextPath, theWantLocalReferences);
1028                                        }
1029                                }
1030                        }
1031                }
1032        }
1033
1034        @Override
1035        public String toRootTypeName(IBase nextObject) {
1036                BaseRuntimeElementDefinition<?> elementDefinition = getContext().getElementDefinition(nextObject.getClass());
1037                BaseRuntimeElementDefinition<?> rootParentDefinition = elementDefinition.getRootParentDefinition();
1038                return rootParentDefinition.getName();
1039        }
1040
1041        @Override
1042        public String toTypeName(IBase nextObject) {
1043                BaseRuntimeElementDefinition<?> elementDefinition = getContext().getElementDefinition(nextObject.getClass());
1044                return elementDefinition.getName();
1045        }
1046
1047        private void addUri_Uri(String theResourceType, Set<ResourceIndexedSearchParamUri> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
1048                IPrimitiveType<?> value = (IPrimitiveType<?>) theValue;
1049                String valueAsString = value.getValueAsString();
1050                if (isNotBlank(valueAsString)) {
1051                        ResourceIndexedSearchParamUri nextEntity = new ResourceIndexedSearchParamUri(myPartitionSettings, theResourceType, theSearchParam.getName(), valueAsString);
1052                        theParams.add(nextEntity);
1053                }
1054        }
1055
1056        @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"})
1057        private void createStringIndexIfNotBlank(String theResourceType, Set<? extends BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, String theValue) {
1058                String value = theValue;
1059                if (isNotBlank(value)) {
1060                        if (value.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
1061                                value = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
1062                        }
1063
1064                        String searchParamName = theSearchParam.getName();
1065                        String valueNormalized = StringUtil.normalizeStringForSearchIndexing(value);
1066                        String valueEncoded = theSearchParam.encode(valueNormalized);
1067
1068                        if (valueEncoded.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
1069                                valueEncoded = valueEncoded.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH);
1070                        }
1071
1072                        ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(myPartitionSettings, getModelConfig(), theResourceType, searchParamName, valueEncoded, value);
1073
1074                        Set params = theParams;
1075                        params.add(nextEntity);
1076                }
1077        }
1078
1079        private void createTokenIndexIfNotBlankAndAdd(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, String theSystem, String theValue) {
1080                ResourceIndexedSearchParamToken nextEntity = createTokenIndexIfNotBlank(theResourceType, theSystem, theValue, theSearchParam.getName());
1081                if (nextEntity != null) {
1082                        theParams.add(nextEntity);
1083                }
1084        }
1085
1086        @VisibleForTesting
1087        public void setPartitionSettings(PartitionSettings thePartitionSettings) {
1088                myPartitionSettings = thePartitionSettings;
1089        }
1090
1091        private ResourceIndexedSearchParamToken createTokenIndexIfNotBlank(String theResourceType, String theSystem, String theValue, String searchParamName) {
1092                String system = theSystem;
1093                String value = theValue;
1094                ResourceIndexedSearchParamToken nextEntity = null;
1095                if (isNotBlank(system) || isNotBlank(value)) {
1096                        if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
1097                                system = system.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH);
1098                        }
1099                        if (value != null && value.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
1100                                value = value.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH);
1101                        }
1102
1103                        nextEntity = new ResourceIndexedSearchParamToken(myPartitionSettings, theResourceType, searchParamName, system, value);
1104                }
1105
1106                return nextEntity;
1107        }
1108
1109        @Override
1110        public String[] split(String thePaths) {
1111                if (shouldAttemptToSplitPath(thePaths)) {
1112                        return splitOutOfParensOrs(thePaths);
1113                } else {
1114                        return new String[]{thePaths};
1115                }
1116        }
1117
1118        public boolean shouldAttemptToSplitPath(String thePath) {
1119                if (getContext().getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
1120                        if (thePath.contains("|")) {
1121                                return true;
1122                        }
1123                } else {
1124                        //DSTU 3 and below used "or" as well as "|"
1125                        if (thePath.contains("|") || thePath.contains(" or ")) {
1126                                return true;
1127                        }
1128                }
1129                return false;
1130        }
1131
1132        /**
1133         * Iteratively splits a string on any ` or ` or | that is ** not** contained inside a set of parentheses. e.g.
1134         *
1135         * "Patient.select(a or b)" -->  ["Patient.select(a or b)"]
1136         * "Patient.select(a or b) or Patient.select(c or d )" --> ["Patient.select(a or b)", "Patient.select(c or d)"]
1137         * "Patient.select(a|b) or Patient.select(c or d )" --> ["Patient.select(a|b)", "Patient.select(c or d)"]
1138         * "Patient.select(b) | Patient.select(c)" -->  ["Patient.select(b)", "Patient.select(c)"]
1139         *
1140         * @param thePaths The string to split
1141         * @return The split string
1142
1143         */
1144        private String[] splitOutOfParensOrs(String thePaths) {
1145                List<String> topLevelOrExpressions = splitOutOfParensToken(thePaths, " or ");
1146                List<String> retVal = topLevelOrExpressions.stream()
1147                        .flatMap(s -> splitOutOfParensToken(s, " |").stream())
1148                        .collect(Collectors.toList());
1149                return retVal.toArray(new String[retVal.size()]);
1150        }
1151
1152        private List<String> splitOutOfParensToken(String thePath, String theToken) {
1153                int tokenLength = theToken.length();
1154                int index = thePath.indexOf(theToken);
1155                int rightIndex = 0;
1156                List<String> retVal = new ArrayList<>();
1157                while (index > -1 ) {
1158                        String left = thePath.substring(rightIndex, index);
1159                        if (allParensHaveBeenClosed(left)) {
1160                                retVal.add(left);
1161                                rightIndex = index + tokenLength;
1162                        }
1163                        index = thePath.indexOf(theToken, index + tokenLength);
1164                }
1165                retVal.add(thePath.substring(rightIndex));
1166                return retVal;
1167        }
1168
1169        private boolean allParensHaveBeenClosed(String thePaths) {
1170                int open = StringUtils.countMatches(thePaths, "(");
1171                int close = StringUtils.countMatches(thePaths, ")");
1172                return open == close;
1173        }
1174
1175        private BigDecimal normalizeQuantityContainingTimeUnitsIntoDaysForNumberParam(String theSystem, String theCode, BigDecimal theValue) {
1176                if (SearchParamConstants.UCUM_NS.equals(theSystem)) {
1177                        if (isNotBlank(theCode)) {
1178                                Unit<? extends Quantity> unit = Unit.valueOf(theCode);
1179                                javax.measure.converter.UnitConverter dayConverter = unit.getConverterTo(NonSI.DAY);
1180                                double dayValue = dayConverter.convert(theValue.doubleValue());
1181                                theValue = new BigDecimal(dayValue);
1182                        }
1183                }
1184                return theValue;
1185        }
1186
1187        @PostConstruct
1188        public void start() {
1189                myIgnoredForSearchDatatypes = new HashSet<>();
1190                addIgnoredType(getContext(), "Annotation", myIgnoredForSearchDatatypes);
1191                addIgnoredType(getContext(), "Attachment", myIgnoredForSearchDatatypes);
1192                addIgnoredType(getContext(), "Count", myIgnoredForSearchDatatypes);
1193                addIgnoredType(getContext(), "Distance", myIgnoredForSearchDatatypes);
1194                addIgnoredType(getContext(), "Ratio", myIgnoredForSearchDatatypes);
1195                addIgnoredType(getContext(), "SampledData", myIgnoredForSearchDatatypes);
1196                addIgnoredType(getContext(), "Signature", myIgnoredForSearchDatatypes);
1197
1198                /*
1199                 * This is building up an internal map of all the various field accessors we'll need in order to work
1200                 * with the model. This is kind of ugly, but we want to be as efficient as possible since
1201                 * search param extraction happens a whole heck of a lot at runtime..
1202                 */
1203
1204                BaseRuntimeElementCompositeDefinition<?> quantityDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Quantity");
1205                myQuantityValueValueChild = quantityDefinition.getChildByName("value");
1206                myQuantitySystemValueChild = quantityDefinition.getChildByName("system");
1207                myQuantityCodeValueChild = quantityDefinition.getChildByName("code");
1208
1209                BaseRuntimeElementCompositeDefinition<?> moneyDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Money");
1210                myMoneyValueChild = moneyDefinition.getChildByName("value");
1211                myMoneyCurrencyChild = moneyDefinition.getChildByName("currency");
1212
1213                BaseRuntimeElementCompositeDefinition<?> locationDefinition = getContext().getResourceDefinition("Location");
1214                BaseRuntimeChildDefinition locationPositionValueChild = locationDefinition.getChildByName("position");
1215                myLocationPositionDefinition = (BaseRuntimeElementCompositeDefinition<?>) locationPositionValueChild.getChildByName("position");
1216
1217                BaseRuntimeElementCompositeDefinition<?> codeSystemDefinition;
1218                if (getContext().getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
1219                        codeSystemDefinition = getContext().getResourceDefinition("CodeSystem");
1220                        assert codeSystemDefinition != null;
1221                        myCodeSystemUrlValueChild = codeSystemDefinition.getChildByName("url");
1222                }
1223
1224                BaseRuntimeElementCompositeDefinition<?> rangeDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Range");
1225                myRangeLowValueChild = rangeDefinition.getChildByName("low");
1226                myRangeHighValueChild = rangeDefinition.getChildByName("high");
1227
1228                BaseRuntimeElementCompositeDefinition<?> addressDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Address");
1229                myAddressLineValueChild = addressDefinition.getChildByName("line");
1230                myAddressCityValueChild = addressDefinition.getChildByName("city");
1231                myAddressStateValueChild = addressDefinition.getChildByName("state");
1232                myAddressCountryValueChild = addressDefinition.getChildByName("country");
1233                myAddressPostalCodeValueChild = addressDefinition.getChildByName("postalCode");
1234
1235                if (getContext().getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
1236                        BaseRuntimeElementCompositeDefinition<?> capabilityStatementDefinition = getContext().getResourceDefinition("CapabilityStatement");
1237                        BaseRuntimeChildDefinition capabilityStatementRestChild = capabilityStatementDefinition.getChildByName("rest");
1238                        BaseRuntimeElementCompositeDefinition<?> capabilityStatementRestDefinition = (BaseRuntimeElementCompositeDefinition<?>) capabilityStatementRestChild.getChildByName("rest");
1239                        BaseRuntimeChildDefinition capabilityStatementRestSecurityValueChild = capabilityStatementRestDefinition.getChildByName("security");
1240                        BaseRuntimeElementCompositeDefinition<?> capabilityStatementRestSecurityDefinition = (BaseRuntimeElementCompositeDefinition<?>) capabilityStatementRestSecurityValueChild.getChildByName("security");
1241                        myCapabilityStatementRestSecurityServiceValueChild = capabilityStatementRestSecurityDefinition.getChildByName("service");
1242                }
1243
1244                BaseRuntimeElementCompositeDefinition<?> periodDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Period");
1245                myPeriodStartValueChild = periodDefinition.getChildByName("start");
1246                myPeriodEndValueChild = periodDefinition.getChildByName("end");
1247
1248                BaseRuntimeElementCompositeDefinition<?> timingDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Timing");
1249                myTimingEventValueChild = timingDefinition.getChildByName("event");
1250                myTimingRepeatValueChild = timingDefinition.getChildByName("repeat");
1251                BaseRuntimeElementCompositeDefinition<?> timingRepeatDefinition = (BaseRuntimeElementCompositeDefinition<?>) myTimingRepeatValueChild.getChildByName("repeat");
1252                myTimingRepeatBoundsValueChild = timingRepeatDefinition.getChildByName("bounds[x]");
1253
1254                BaseRuntimeElementCompositeDefinition<?> durationDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Duration");
1255                myDurationSystemValueChild = durationDefinition.getChildByName("system");
1256                myDurationCodeValueChild = durationDefinition.getChildByName("code");
1257                myDurationValueValueChild = durationDefinition.getChildByName("value");
1258
1259                BaseRuntimeElementCompositeDefinition<?> humanNameDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("HumanName");
1260                myHumanNameFamilyValueChild = humanNameDefinition.getChildByName("family");
1261                myHumanNameGivenValueChild = humanNameDefinition.getChildByName("given");
1262                myHumanNameTextValueChild = humanNameDefinition.getChildByName("text");
1263                myHumanNamePrefixValueChild = humanNameDefinition.getChildByName("prefix");
1264                myHumanNameSuffixValueChild = humanNameDefinition.getChildByName("suffix");
1265
1266                BaseRuntimeElementCompositeDefinition<?> contactPointDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("ContactPoint");
1267                myContactPointValueValueChild = contactPointDefinition.getChildByName("value");
1268                myContactPointSystemValueChild = contactPointDefinition.getChildByName("system");
1269
1270                BaseRuntimeElementCompositeDefinition<?> identifierDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Identifier");
1271                myIdentifierSystemValueChild = identifierDefinition.getChildByName("system");
1272                myIdentifierValueValueChild = identifierDefinition.getChildByName("value");
1273                myIdentifierTypeValueChild = identifierDefinition.getChildByName("type");
1274                BaseRuntimeElementCompositeDefinition<?> identifierTypeDefinition = (BaseRuntimeElementCompositeDefinition<?>) myIdentifierTypeValueChild.getChildByName("type");
1275                myIdentifierTypeTextValueChild = identifierTypeDefinition.getChildByName("text");
1276
1277                BaseRuntimeElementCompositeDefinition<?> codeableConceptDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("CodeableConcept");
1278                myCodeableConceptCodingValueChild = codeableConceptDefinition.getChildByName("coding");
1279                myCodeableConceptTextValueChild = codeableConceptDefinition.getChildByName("text");
1280
1281                BaseRuntimeElementCompositeDefinition<?> codingDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("Coding");
1282                myCodingSystemValueChild = codingDefinition.getChildByName("system");
1283                myCodingCodeValueChild = codingDefinition.getChildByName("code");
1284                myCodingDisplayValueChild = codingDefinition.getChildByName("display");
1285
1286                BaseRuntimeElementCompositeDefinition<?> patientDefinition = getContext().getResourceDefinition("Patient");
1287                BaseRuntimeChildDefinition patientCommunicationValueChild = patientDefinition.getChildByName("communication");
1288                BaseRuntimeElementCompositeDefinition<?> patientCommunicationDefinition = (BaseRuntimeElementCompositeDefinition<?>) patientCommunicationValueChild.getChildByName("communication");
1289                myPatientCommunicationLanguageValueChild = patientCommunicationDefinition.getChildByName("language");
1290
1291        }
1292
1293        @FunctionalInterface
1294        public interface IValueExtractor {
1295
1296                List<? extends IBase> get() throws FHIRException;
1297
1298        }
1299
1300        @FunctionalInterface
1301        private interface IExtractor<T> {
1302
1303                void extract(SearchParamSet<T> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath, boolean theWantLocalReferences);
1304
1305        }
1306
1307        /**
1308         * Note that this should only be called for R4+ servers. Prior to
1309         * R4 the paths could be separated by the word "or" or by a "|"
1310         * character, so we used a slower splitting mechanism.
1311         */
1312        @Nonnull
1313        public static String[] splitPathsR4(@Nonnull String thePaths) {
1314                StringTokenizer tok = new StringTokenizer(thePaths, " |");
1315                tok.setTrimmerMatcher(new StringTrimmingTrimmerMatcher());
1316                return tok.getTokenArray();
1317        }
1318
1319        public static boolean tokenTextIndexingEnabledForSearchParam(ModelConfig theModelConfig, RuntimeSearchParam theSearchParam) {
1320                Optional<Boolean> noSuppressForSearchParam = theSearchParam.getExtensions(HapiExtensions.EXT_SEARCHPARAM_TOKEN_SUPPRESS_TEXT_INDEXING).stream()
1321                        .map(IBaseExtension::getValue)
1322                        .map(val -> (IPrimitiveType<?>) val)
1323                        .map(IPrimitiveType::getValueAsString)
1324                        .map(Boolean::parseBoolean)
1325                        .findFirst();
1326
1327                //if the SP doesn't care, use the system default.
1328                if (!noSuppressForSearchParam.isPresent()) {
1329                        return !theModelConfig.isSuppressStringIndexingInTokens();
1330                        //If the SP does care, use its value.
1331                } else {
1332                        boolean suppressForSearchParam = noSuppressForSearchParam.get();
1333                        ourLog.trace("Text indexing for SearchParameter {}: {}", theSearchParam.getName(), suppressForSearchParam);
1334                        return !suppressForSearchParam;
1335                }
1336        }
1337
1338        private static void addIgnoredType(FhirContext theCtx, String theType, Set<String> theIgnoredTypes) {
1339                BaseRuntimeElementDefinition<?> elementDefinition = theCtx.getElementDefinition(theType);
1340                if (elementDefinition != null) {
1341                        theIgnoredTypes.add(elementDefinition.getName());
1342                }
1343        }
1344
1345        protected static String extractValueAsString(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) {
1346                return theChildDefinition
1347                        .getAccessor()
1348                        .<IPrimitiveType<?>>getFirstValueOrNull(theElement)
1349                        .map(IPrimitiveType::getValueAsString)
1350                        .orElse(null);
1351        }
1352
1353        protected static Date extractValueAsDate(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) {
1354                return theChildDefinition
1355                        .getAccessor()
1356                        .<IPrimitiveType<Date>>getFirstValueOrNull(theElement)
1357                        .map(IPrimitiveType::getValue)
1358                        .orElse(null);
1359        }
1360
1361        protected static BigDecimal extractValueAsBigDecimal(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) {
1362                return theChildDefinition
1363                        .getAccessor()
1364                        .<IPrimitiveType<BigDecimal>>getFirstValueOrNull(theElement)
1365                        .map(IPrimitiveType::getValue)
1366                        .orElse(null);
1367        }
1368
1369        @SuppressWarnings("unchecked")
1370        protected static List<IPrimitiveType<Date>> extractValuesAsFhirDates(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) {
1371                return (List) theChildDefinition
1372                        .getAccessor()
1373                        .getValues(theElement);
1374        }
1375
1376        protected static List<String> extractValuesAsStrings(BaseRuntimeChildDefinition theChildDefinition, IBase theValue) {
1377                return theChildDefinition
1378                        .getAccessor()
1379                        .getValues(theValue)
1380                        .stream()
1381                        .map(t -> (IPrimitiveType) t)
1382                        .map(IPrimitiveType::getValueAsString)
1383                        .filter(StringUtils::isNotBlank)
1384                        .collect(Collectors.toList());
1385        }
1386
1387        protected static <T extends Enum<?>> String extractSystem(IBaseEnumeration<T> theBoundCode) {
1388                if (theBoundCode.getValue() != null) {
1389                        return theBoundCode.getEnumFactory().toSystem(theBoundCode.getValue());
1390                }
1391                return null;
1392        }
1393
1394        private class ResourceLinkExtractor implements IExtractor<PathAndRef> {
1395
1396                private PathAndRef myPathAndRef = null;
1397
1398                @Override
1399                public void extract(SearchParamSet<PathAndRef> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath, boolean theWantLocalReferences) {
1400                        if (theValue instanceof IBaseResource) {
1401                                return;
1402                        }
1403
1404                        String nextType = toRootTypeName(theValue);
1405                        switch (nextType) {
1406                                case "uri":
1407                                case "canonical":
1408                                        String typeName = toTypeName(theValue);
1409                                        IPrimitiveType<?> valuePrimitive = (IPrimitiveType<?>) theValue;
1410                                        IBaseReference fakeReference = (IBaseReference) myContext.getElementDefinition("Reference").newInstance();
1411                                        fakeReference.setReference(valuePrimitive.getValueAsString());
1412
1413                                        // Canonical has a root type of "uri"
1414                                        if ("canonical".equals(typeName)) {
1415
1416                                                /*
1417                                                 * See #1583
1418                                                 * Technically canonical fields should not allow local references (e.g.
1419                                                 * Questionnaire/123) but it seems reasonable for us to interpret a canonical
1420                                                 * containing a local reference for what it is, and allow people to search
1421                                                 * based on that.
1422                                                 */
1423                                                IIdType parsed = fakeReference.getReferenceElement();
1424                                                if (parsed.hasIdPart() && parsed.hasResourceType() && !parsed.isAbsolute()) {
1425                                                        myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, fakeReference, false);
1426                                                        theParams.add(myPathAndRef);
1427                                                        break;
1428                                                }
1429
1430                                                if (parsed.isAbsolute()) {
1431                                                        myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, fakeReference, true);
1432                                                        theParams.add(myPathAndRef);
1433                                                        break;
1434                                                }
1435                                        }
1436
1437                                        theParams.addWarning("Ignoring canonical reference (indexing canonical is not yet supported)");
1438                                        break;
1439                                case "reference":
1440                                case "Reference":
1441                                        IBaseReference valueRef = (IBaseReference) theValue;
1442
1443                                        IIdType nextId = valueRef.getReferenceElement();
1444                                        if (nextId.isEmpty() && valueRef.getResource() != null) {
1445                                                nextId = valueRef.getResource().getIdElement();
1446                                        }
1447
1448                                        if (nextId == null ||
1449                                                nextId.isEmpty() ||
1450                                                nextId.getValue().startsWith("urn:")) {
1451                                                return;
1452                                        }
1453                                        if (!theWantLocalReferences) {
1454                                                if (nextId.getValue().startsWith("#"))
1455                                                        return;
1456                                        }
1457
1458                                        myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, valueRef, false);
1459                                        theParams.add(myPathAndRef);
1460                                        break;
1461                                default:
1462                                        addUnexpectedDatatypeWarning(theParams, theSearchParam, theValue);
1463                                        break;
1464                        }
1465                }
1466
1467                public PathAndRef get(IBase theValue, String thePath) {
1468                        extract(new SearchParamSet<>(),
1469                                new RuntimeSearchParam(null, null, "Reference", null, null, null, null, null, null, null),
1470                                theValue, thePath, false);
1471                        return myPathAndRef;
1472                }
1473        }
1474
1475        private class DateExtractor implements IExtractor<ResourceIndexedSearchParamDate> {
1476
1477                String myResourceType;
1478                ResourceIndexedSearchParamDate myIndexedSearchParamDate = null;
1479
1480                public DateExtractor(IBaseResource theResource) {
1481                        myResourceType = toRootTypeName(theResource);
1482                }
1483
1484                public DateExtractor(String theResourceType) {
1485                        myResourceType = theResourceType;
1486                }
1487
1488                @Override
1489                public void extract(SearchParamSet theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath, boolean theWantLocalReferences) {
1490                        String nextType = toRootTypeName(theValue);
1491                        switch (nextType) {
1492                                case "date":
1493                                case "dateTime":
1494                                case "instant":
1495                                        addDateTimeTypes(myResourceType, theParams, theSearchParam, theValue);
1496                                        break;
1497                                case "Period":
1498                                        addDate_Period(myResourceType, theParams, theSearchParam, theValue);
1499                                        break;
1500                                case "Timing":
1501                                        addDate_Timing(myResourceType, theParams, theSearchParam, theValue);
1502                                        break;
1503                                case "string":
1504                                        // CarePlan.activitydate can be a string - ignored for now
1505                                        break;
1506                                default:
1507                                        addUnexpectedDatatypeWarning(theParams, theSearchParam, theValue);
1508                                        break;
1509
1510                        }
1511                }
1512
1513                private void addDate_Period(String theResourceType, Set<ResourceIndexedSearchParamDate> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
1514                        Date start = extractValueAsDate(myPeriodStartValueChild, theValue);
1515                        String startAsString = extractValueAsString(myPeriodStartValueChild, theValue);
1516                        Date end = extractValueAsDate(myPeriodEndValueChild, theValue);
1517                        String endAsString = extractValueAsString(myPeriodEndValueChild, theValue);
1518
1519                        if (start != null || end != null) {
1520
1521                                if (start == null) {
1522                                        start = myModelConfig.getPeriodIndexStartOfTime().getValue();
1523                                        startAsString = myModelConfig.getPeriodIndexStartOfTime().getValueAsString();
1524                                }
1525                                if (end == null) {
1526                                        end = myModelConfig.getPeriodIndexEndOfTime().getValue();
1527                                        endAsString = myModelConfig.getPeriodIndexEndOfTime().getValueAsString();
1528                                }
1529
1530                                myIndexedSearchParamDate = new ResourceIndexedSearchParamDate(myPartitionSettings, theResourceType, theSearchParam.getName(), start, startAsString, end, endAsString, startAsString);
1531                                theParams.add(myIndexedSearchParamDate);
1532                        }
1533                }
1534
1535                private void addDate_Timing(String theResourceType, Set<ResourceIndexedSearchParamDate> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
1536                        List<IPrimitiveType<Date>> values = extractValuesAsFhirDates(myTimingEventValueChild, theValue);
1537
1538                        TreeSet<Date> dates = new TreeSet<>();
1539                        String firstValue = null;
1540                        String finalValue = null;
1541                        for (IPrimitiveType<Date> nextEvent : values) {
1542                                if (nextEvent.getValue() != null) {
1543                                        dates.add(nextEvent.getValue());
1544                                        if (firstValue == null) {
1545                                                firstValue = nextEvent.getValueAsString();
1546                                        }
1547                                        finalValue = nextEvent.getValueAsString();
1548                                }
1549                        }
1550
1551                        Optional<IBase> repeat = myTimingRepeatValueChild.getAccessor().getFirstValueOrNull(theValue);
1552                        if (repeat.isPresent()) {
1553                                Optional<IBase> bounds = myTimingRepeatBoundsValueChild.getAccessor().getFirstValueOrNull(repeat.get());
1554                                if (bounds.isPresent()) {
1555                                        String boundsType = toRootTypeName(bounds.get());
1556                                        if ("Period".equals(boundsType)) {
1557                                                Date start = extractValueAsDate(myPeriodStartValueChild, bounds.get());
1558                                                Date end = extractValueAsDate(myPeriodEndValueChild, bounds.get());
1559                                                if (start != null) {
1560                                                        dates.add(start);
1561                                                }
1562                                                if (end != null) {
1563                                                        dates.add(end);
1564                                                }
1565                                        }
1566                                }
1567                        }
1568
1569                        if (!dates.isEmpty()) {
1570                                myIndexedSearchParamDate = new ResourceIndexedSearchParamDate(myPartitionSettings, theResourceType, theSearchParam.getName(), dates.first(), firstValue, dates.last(), finalValue, firstValue);
1571                                theParams.add(myIndexedSearchParamDate);
1572                        }
1573                }
1574
1575                @SuppressWarnings("unchecked")
1576                private void addDateTimeTypes(String theResourceType, Set<ResourceIndexedSearchParamDate> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
1577                        IPrimitiveType<Date> nextBaseDateTime = (IPrimitiveType<Date>) theValue;
1578                        if (nextBaseDateTime.getValue() != null) {
1579                                myIndexedSearchParamDate = new ResourceIndexedSearchParamDate(myPartitionSettings, theResourceType, theSearchParam.getName(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString(), nextBaseDateTime.getValueAsString());
1580                                theParams.add(myIndexedSearchParamDate);
1581                        }
1582                }
1583
1584                public ResourceIndexedSearchParamDate get(IBase theValue, String thePath, boolean theWantLocalReferences) {
1585                        extract(new SearchParamSet<>(),
1586                                new RuntimeSearchParam(null, null, "date", null, null, null, null, null, null, null),
1587                                theValue, thePath, theWantLocalReferences);
1588                        return myIndexedSearchParamDate;
1589                }
1590        }
1591
1592        private class TokenExtractor implements IExtractor<BaseResourceIndexedSearchParam> {
1593                private final String myResourceTypeName;
1594                private final String myUseSystem;
1595
1596                public TokenExtractor(String theResourceTypeName, String theUseSystem) {
1597                        myResourceTypeName = theResourceTypeName;
1598                        myUseSystem = theUseSystem;
1599                }
1600
1601                @Override
1602                public void extract(SearchParamSet<BaseResourceIndexedSearchParam> params, RuntimeSearchParam searchParam, IBase value, String path, boolean theWantLocalReferences) {
1603
1604                        // DSTU3+
1605                        if (value instanceof IBaseEnumeration<?>) {
1606                                IBaseEnumeration<?> obj = (IBaseEnumeration<?>) value;
1607                                String system = extractSystem(obj);
1608                                String code = obj.getValueAsString();
1609                                BaseSearchParamExtractor.this.createTokenIndexIfNotBlankAndAdd(myResourceTypeName, params, searchParam, system, code);
1610                                return;
1611                        }
1612
1613                        // DSTU2 only
1614                        if (value instanceof BoundCodeDt) {
1615                                BoundCodeDt boundCode = (BoundCodeDt) value;
1616                                Enum valueAsEnum = boundCode.getValueAsEnum();
1617                                String system = null;
1618                                if (valueAsEnum != null) {
1619                                        //noinspection unchecked
1620                                        system = boundCode.getBinder().toSystemString(valueAsEnum);
1621                                }
1622                                String code = boundCode.getValueAsString();
1623                                BaseSearchParamExtractor.this.createTokenIndexIfNotBlankAndAdd(myResourceTypeName, params, searchParam, system, code);
1624                                return;
1625                        }
1626
1627                        if (value instanceof IPrimitiveType) {
1628                                IPrimitiveType<?> nextValue = (IPrimitiveType<?>) value;
1629                                String systemAsString = null;
1630                                String valueAsString = nextValue.getValueAsString();
1631                                if ("CodeSystem.concept.code".equals(path)) {
1632                                        systemAsString = myUseSystem;
1633                                } else if ("ValueSet.codeSystem.concept.code".equals(path)) {
1634                                        systemAsString = myUseSystem;
1635                                }
1636
1637                                if (value instanceof IIdType) {
1638                                        valueAsString = ((IIdType) value).getIdPart();
1639                                }
1640
1641                                BaseSearchParamExtractor.this.createTokenIndexIfNotBlankAndAdd(myResourceTypeName, params, searchParam, systemAsString, valueAsString);
1642                                return;
1643                        }
1644
1645                        switch (path) {
1646                                case "Patient.communication":
1647                                        BaseSearchParamExtractor.this.addToken_PatientCommunication(myResourceTypeName, params, searchParam, value);
1648                                        return;
1649                                case "Consent.source":
1650                                        // Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that
1651                                        return;
1652                                case "Location.position":
1653                                        BaseSearchParamExtractor.this.addCoords_Position(myResourceTypeName, params, searchParam, value);
1654                                        return;
1655                                case "StructureDefinition.context":
1656                                        // TODO: implement this
1657                                        ourLog.warn("StructureDefinition context indexing not currently supported");
1658                                        return;
1659                                case "CapabilityStatement.rest.security":
1660                                        BaseSearchParamExtractor.this.addToken_CapabilityStatementRestSecurity(myResourceTypeName, params, searchParam, value);
1661                                        return;
1662                        }
1663
1664                        String nextType = BaseSearchParamExtractor.this.toRootTypeName(value);
1665                        switch (nextType) {
1666                                case "Identifier":
1667                                        BaseSearchParamExtractor.this.addToken_Identifier(myResourceTypeName, params, searchParam, value);
1668                                        break;
1669                                case "CodeableConcept":
1670                                        BaseSearchParamExtractor.this.addToken_CodeableConcept(myResourceTypeName, params, searchParam, value);
1671                                        break;
1672                                case "Coding":
1673                                        BaseSearchParamExtractor.this.addToken_Coding(myResourceTypeName, params, searchParam, value);
1674                                        break;
1675                                case "ContactPoint":
1676                                        BaseSearchParamExtractor.this.addToken_ContactPoint(myResourceTypeName, params, searchParam, value);
1677                                        break;
1678                                default:
1679                                        BaseSearchParamExtractor.this.addUnexpectedDatatypeWarning(params, searchParam, value);
1680                                        break;
1681                        }
1682                }
1683        }
1684
1685        private static class CompositeExtractor<T> implements IExtractor<T> {
1686
1687                private final IExtractor<T> myExtractor0;
1688                private final IExtractor<T> myExtractor1;
1689
1690                private CompositeExtractor(IExtractor<T> theExtractor0, IExtractor<T> theExtractor1) {
1691                        myExtractor0 = theExtractor0;
1692                        myExtractor1 = theExtractor1;
1693                }
1694
1695                @Override
1696                public void extract(SearchParamSet<T> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath, boolean theWantLocalReferences) {
1697                        myExtractor0.extract(theParams, theSearchParam, theValue, thePath, theWantLocalReferences);
1698                        myExtractor1.extract(theParams, theSearchParam, theValue, thePath, theWantLocalReferences);
1699                }
1700        }
1701
1702}