001package ca.uhn.fhir.jpa.searchparam;
002
003import ca.uhn.fhir.context.FhirContext;
004import ca.uhn.fhir.model.api.IQueryParameterAnd;
005import ca.uhn.fhir.model.api.IQueryParameterOr;
006import ca.uhn.fhir.model.api.IQueryParameterType;
007import ca.uhn.fhir.model.api.Include;
008import ca.uhn.fhir.rest.api.Constants;
009import ca.uhn.fhir.rest.api.SearchContainedModeEnum;
010import ca.uhn.fhir.rest.api.SearchTotalModeEnum;
011import ca.uhn.fhir.rest.api.SortOrderEnum;
012import ca.uhn.fhir.rest.api.SortSpec;
013import ca.uhn.fhir.rest.api.SummaryEnum;
014import ca.uhn.fhir.rest.param.DateParam;
015import ca.uhn.fhir.rest.param.DateRangeParam;
016import ca.uhn.fhir.rest.param.ParamPrefixEnum;
017import ca.uhn.fhir.rest.param.QuantityParam;
018import ca.uhn.fhir.rest.param.TokenParamModifier;
019import ca.uhn.fhir.util.ObjectUtil;
020import ca.uhn.fhir.util.UrlUtil;
021import com.fasterxml.jackson.annotation.JsonIgnore;
022import org.apache.commons.lang3.StringUtils;
023import org.apache.commons.lang3.Validate;
024import org.apache.commons.lang3.builder.CompareToBuilder;
025import org.apache.commons.lang3.builder.ToStringBuilder;
026import org.apache.commons.lang3.builder.ToStringStyle;
027
028import javax.annotation.Nonnull;
029import java.io.Serializable;
030import java.util.ArrayList;
031import java.util.Collection;
032import java.util.Collections;
033import java.util.Comparator;
034import java.util.HashMap;
035import java.util.HashSet;
036import java.util.LinkedHashMap;
037import java.util.List;
038import java.util.Map;
039import java.util.Set;
040import java.util.stream.Collectors;
041
042import static ca.uhn.fhir.rest.param.ParamPrefixEnum.GREATERTHAN_OR_EQUALS;
043import static ca.uhn.fhir.rest.param.ParamPrefixEnum.LESSTHAN_OR_EQUALS;
044import static ca.uhn.fhir.rest.param.ParamPrefixEnum.NOT_EQUAL;
045import static org.apache.commons.lang3.StringUtils.isBlank;
046import static org.apache.commons.lang3.StringUtils.isNotBlank;
047
048/*
049 * #%L
050 * HAPI FHIR Search Parameters
051 * %%
052 * Copyright (C) 2014 - 2022 Smile CDR, Inc.
053 * %%
054 * Licensed under the Apache License, Version 2.0 (the "License");
055 * you may not use this file except in compliance with the License.
056 * You may obtain a copy of the License at
057 *
058 * http://www.apache.org/licenses/LICENSE-2.0
059 *
060 * Unless required by applicable law or agreed to in writing, software
061 * distributed under the License is distributed on an "AS IS" BASIS,
062 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
063 * See the License for the specific language governing permissions and
064 * limitations under the License.
065 * #L%
066 */
067
068public class SearchParameterMap implements Serializable {
069        public static final Integer INTEGER_0 = 0;
070        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParameterMap.class);
071        private static final long serialVersionUID = 1L;
072        private final HashMap<String, List<List<IQueryParameterType>>> mySearchParameterMap = new LinkedHashMap<>();
073        private Integer myCount;
074        private Integer myOffset;
075        private EverythingModeEnum myEverythingMode = null;
076        private Set<Include> myIncludes;
077        private DateRangeParam myLastUpdated;
078        private boolean myLoadSynchronous;
079        private Integer myLoadSynchronousUpTo;
080        private Set<Include> myRevIncludes;
081        private SortSpec mySort;
082        private SummaryEnum mySummaryMode;
083        private SearchTotalModeEnum mySearchTotalMode;
084        private QuantityParam myNearDistanceParam;
085        private boolean myLastN;
086        private Integer myLastNMax;
087        private boolean myDeleteExpunge;
088        private SearchContainedModeEnum mySearchContainedMode = SearchContainedModeEnum.FALSE;
089
090        /**
091         * Constructor
092         */
093        public SearchParameterMap() {
094                super();
095        }
096
097        /**
098         * Constructor
099         */
100        public SearchParameterMap(String theName, IQueryParameterType theParam) {
101                add(theName, theParam);
102        }
103
104        /**
105         * Creates and returns a copy of this map
106         */
107        @JsonIgnore
108        @Override
109        public SearchParameterMap clone() {
110                SearchParameterMap map = new SearchParameterMap();
111                map.setSummaryMode(getSummaryMode());
112                map.setSort(getSort());
113                map.setSearchTotalMode(getSearchTotalMode());
114                map.setRevIncludes(getRevIncludes());
115                map.setIncludes(getIncludes());
116                map.setEverythingMode(getEverythingMode());
117                map.setCount(getCount());
118                map.setDeleteExpunge(isDeleteExpunge());
119                map.setLastN(isLastN());
120                map.setLastNMax(getLastNMax());
121                map.setLastUpdated(getLastUpdated());
122                map.setLoadSynchronous(isLoadSynchronous());
123                map.setNearDistanceParam(getNearDistanceParam());
124                map.setLoadSynchronousUpTo(getLoadSynchronousUpTo());
125                map.setOffset(getOffset());
126                map.setSearchContainedMode(getSearchContainedMode());
127
128                for (Map.Entry<String, List<List<IQueryParameterType>>> entry : mySearchParameterMap.entrySet()) {
129                        List<List<IQueryParameterType>> andParams = entry.getValue();
130                        List<List<IQueryParameterType>> newAndParams = new ArrayList<>();
131                        for (List<IQueryParameterType> orParams : andParams) {
132                                List<IQueryParameterType> newOrParams = new ArrayList<>(orParams);
133                                newAndParams.add(newOrParams);
134                        }
135                        map.put(entry.getKey(), newAndParams);
136                }
137
138
139                return map;
140        }
141
142        public SummaryEnum getSummaryMode() {
143                return mySummaryMode;
144        }
145
146        public void setSummaryMode(SummaryEnum theSummaryMode) {
147                mySummaryMode = theSummaryMode;
148        }
149
150        public SearchTotalModeEnum getSearchTotalMode() {
151                return mySearchTotalMode;
152        }
153
154        public void setSearchTotalMode(SearchTotalModeEnum theSearchTotalMode) {
155                mySearchTotalMode = theSearchTotalMode;
156        }
157
158        public SearchParameterMap add(String theName, DateParam theDateParam) {
159                add(theName, (IQueryParameterOr<?>) theDateParam);
160                return this;
161        }
162
163        public SearchParameterMap add(String theName, IQueryParameterAnd<?> theAnd) {
164                if (theAnd == null) {
165                        return this;
166                }
167                if (!containsKey(theName)) {
168                        put(theName, new ArrayList<>());
169                }
170
171                for (IQueryParameterOr<?> next : theAnd.getValuesAsQueryTokens()) {
172                        if (next == null) {
173                                continue;
174                        }
175                        get(theName).add((List<IQueryParameterType>) next.getValuesAsQueryTokens());
176                }
177                return this;
178        }
179
180        public SearchParameterMap add(String theName, IQueryParameterOr<?> theOr) {
181                if (theOr == null) {
182                        return this;
183                }
184                if (!containsKey(theName)) {
185                        put(theName, new ArrayList<>());
186                }
187
188                get(theName).add((List<IQueryParameterType>) theOr.getValuesAsQueryTokens());
189                return this;
190        }
191
192        public Collection<List<List<IQueryParameterType>>> values() {
193                return mySearchParameterMap.values();
194        }
195
196        public SearchParameterMap add(String theName, IQueryParameterType theParam) {
197                assert !Constants.PARAM_LASTUPDATED.equals(theName); // this has it's own field in the map
198
199                if (theParam == null) {
200                        return this;
201                }
202                if (!containsKey(theName)) {
203                        put(theName, new ArrayList<>());
204                }
205                ArrayList<IQueryParameterType> list = new ArrayList<>();
206                list.add(theParam);
207                get(theName).add(list);
208
209                return this;
210        }
211
212        public SearchParameterMap addInclude(Include theInclude) {
213                getIncludes().add(theInclude);
214                return this;
215        }
216
217        private void addLastUpdateParam(StringBuilder theBuilder, ParamPrefixEnum thePrefix, DateParam theDateParam) {
218                if (theDateParam != null && isNotBlank(theDateParam.getValueAsString())) {
219                        addUrlParamSeparator(theBuilder);
220                        theBuilder.append(Constants.PARAM_LASTUPDATED);
221                        theBuilder.append('=');
222                        theBuilder.append(thePrefix.getValue());
223                        theBuilder.append(theDateParam.getValueAsString());
224                }
225        }
226
227        public SearchParameterMap addRevInclude(Include theInclude) {
228                getRevIncludes().add(theInclude);
229                return this;
230        }
231
232        private void addUrlIncludeParams(StringBuilder b, String paramName, Set<Include> theList) {
233                ArrayList<Include> list = new ArrayList<>(theList);
234
235                list.sort(new IncludeComparator());
236                for (Include nextInclude : list) {
237                        addUrlParamSeparator(b);
238                        b.append(paramName);
239                        if (nextInclude.isRecurse()) {
240                                b.append(Constants.PARAM_INCLUDE_QUALIFIER_RECURSE);
241                        }
242                        b.append('=');
243                        b.append(UrlUtil.escapeUrlParam(nextInclude.getParamType()));
244                        b.append(':');
245                        b.append(UrlUtil.escapeUrlParam(nextInclude.getParamName()));
246                        if (isNotBlank(nextInclude.getParamTargetType())) {
247                                b.append(':');
248                                b.append(nextInclude.getParamTargetType());
249                        }
250                }
251        }
252
253        private void addUrlParamSeparator(StringBuilder theB) {
254                if (theB.length() == 0) {
255                        theB.append('?');
256                } else {
257                        theB.append('&');
258                }
259        }
260
261        public Integer getCount() {
262                return myCount;
263        }
264
265        public SearchParameterMap setCount(Integer theCount) {
266                myCount = theCount;
267                return this;
268        }
269
270        public Integer getOffset() {
271                return myOffset;
272        }
273
274        public void setOffset(Integer theOffset) {
275                myOffset = theOffset;
276        }
277
278        public EverythingModeEnum getEverythingMode() {
279                return myEverythingMode;
280        }
281
282        public void setEverythingMode(EverythingModeEnum theConsolidateMatches) {
283                myEverythingMode = theConsolidateMatches;
284        }
285
286        public Set<Include> getIncludes() {
287                if (myIncludes == null) {
288                        myIncludes = new HashSet<>();
289                }
290                return myIncludes;
291        }
292
293        public void setIncludes(Set<Include> theIncludes) {
294                myIncludes = theIncludes;
295        }
296
297        /**
298         * Returns null if there is no last updated value
299         */
300        public DateRangeParam getLastUpdated() {
301                if (myLastUpdated != null) {
302                        if (myLastUpdated.isEmpty()) {
303                                myLastUpdated = null;
304                        }
305                }
306                return myLastUpdated;
307        }
308
309        public void setLastUpdated(DateRangeParam theLastUpdated) {
310                myLastUpdated = theLastUpdated;
311        }
312
313        /**
314         * If set, tells the server to load these results synchronously, and not to load
315         * more than X results
316         */
317        public Integer getLoadSynchronousUpTo() {
318                return myLoadSynchronousUpTo;
319        }
320
321        /**
322         * If set, tells the server to load these results synchronously, and not to load
323         * more than X results. Note that setting this to a value will also set
324         * {@link #setLoadSynchronous(boolean)} to true
325         */
326        public SearchParameterMap setLoadSynchronousUpTo(Integer theLoadSynchronousUpTo) {
327                myLoadSynchronousUpTo = theLoadSynchronousUpTo;
328                if (myLoadSynchronousUpTo != null) {
329                        setLoadSynchronous(true);
330                }
331                return this;
332        }
333
334        public Set<Include> getRevIncludes() {
335                if (myRevIncludes == null) {
336                        myRevIncludes = new HashSet<>();
337                }
338                return myRevIncludes;
339        }
340
341        public void setRevIncludes(Set<Include> theRevIncludes) {
342                myRevIncludes = theRevIncludes;
343        }
344
345        public SortSpec getSort() {
346                return mySort;
347        }
348
349        public SearchParameterMap setSort(SortSpec theSort) {
350                mySort = theSort;
351                return this;
352        }
353
354        /**
355         * This will only return true if all parameters have no modifier of any kind
356         */
357        public boolean isAllParametersHaveNoModifier() {
358                for (List<List<IQueryParameterType>> nextParamName : values()) {
359                        for (List<IQueryParameterType> nextAnd : nextParamName) {
360                                for (IQueryParameterType nextOr : nextAnd) {
361                                        if (isNotBlank(nextOr.getQueryParameterQualifier())) {
362                                                return false;
363                                        }
364                                }
365                        }
366                }
367                return true;
368        }
369
370        /**
371         * If set, tells the server to load these results synchronously, and not to load
372         * more than X results
373         */
374        public boolean isLoadSynchronous() {
375                return myLoadSynchronous;
376        }
377
378        /**
379         * If set, tells the server to load these results synchronously, and not to load
380         * more than X results
381         */
382        public SearchParameterMap setLoadSynchronous(boolean theLoadSynchronous) {
383                myLoadSynchronous = theLoadSynchronous;
384                return this;
385        }
386
387        /**
388         * If set, tells the server to use an Elasticsearch query to generate a list of
389         * Resource IDs for the LastN operation
390         */
391        public boolean isLastN() {
392                return myLastN;
393        }
394
395        /**
396         * If set, tells the server to use an Elasticsearch query to generate a list of
397         * Resource IDs for the LastN operation
398         */
399        public SearchParameterMap setLastN(boolean theLastN) {
400                myLastN = theLastN;
401                return this;
402        }
403
404
405        /**
406         * If set, tells the server the maximum number of observations to return for each
407         * observation code in the result set of a lastn operation
408         */
409        public Integer getLastNMax() {
410                return myLastNMax;
411        }
412
413        /**
414         * If set, tells the server the maximum number of observations to return for each
415         * observation code in the result set of a lastn operation
416         */
417        public SearchParameterMap setLastNMax(Integer theLastNMax) {
418                myLastNMax = theLastNMax;
419                return this;
420        }
421
422
423        /**
424         * This method creates a URL query string representation of the parameters in this
425         * object, excluding the part before the parameters, e.g.
426         * <p>
427         * <code>?name=smith&amp;_sort=Patient:family</code>
428         * </p>
429         * <p>
430         * This method <b>excludes</b> the <code>_count</code> parameter,
431         * as it doesn't affect the substance of the results returned
432         * </p>
433         */
434        public String toNormalizedQueryString(FhirContext theCtx) {
435                StringBuilder b = new StringBuilder();
436
437                ArrayList<String> keys = new ArrayList<>(keySet());
438                Collections.sort(keys);
439                for (String nextKey : keys) {
440
441                        List<List<IQueryParameterType>> nextValuesAndsIn = get(nextKey);
442                        List<List<IQueryParameterType>> nextValuesAndsOut = new ArrayList<>();
443
444                        for (List<? extends IQueryParameterType> nextValuesAndIn : nextValuesAndsIn) {
445
446                                List<IQueryParameterType> nextValuesOrsOut = new ArrayList<>();
447
448                                nextValuesOrsOut.addAll(nextValuesAndIn);
449
450                                nextValuesOrsOut.sort(new QueryParameterTypeComparator(theCtx));
451
452                                if (nextValuesOrsOut.size() > 0) {
453                                        nextValuesAndsOut.add(nextValuesOrsOut);
454                                }
455
456                        } // for AND
457
458                        nextValuesAndsOut.sort(new QueryParameterOrComparator(theCtx));
459
460                        for (List<IQueryParameterType> nextValuesAnd : nextValuesAndsOut) {
461                                addUrlParamSeparator(b);
462                                IQueryParameterType firstValue = nextValuesAnd.get(0);
463                                b.append(UrlUtil.escapeUrlParam(nextKey));
464
465                                if (firstValue.getMissing() != null) {
466                                        b.append(Constants.PARAMQUALIFIER_MISSING);
467                                        b.append('=');
468                                        if (firstValue.getMissing()) {
469                                                b.append(Constants.PARAMQUALIFIER_MISSING_TRUE);
470                                        } else {
471                                                b.append(Constants.PARAMQUALIFIER_MISSING_FALSE);
472                                        }
473                                        continue;
474                                }
475
476                                if (isNotBlank(firstValue.getQueryParameterQualifier())) {
477                                        b.append(firstValue.getQueryParameterQualifier());
478                                }
479
480                                b.append('=');
481
482                                for (int i = 0; i < nextValuesAnd.size(); i++) {
483                                        IQueryParameterType nextValueOr = nextValuesAnd.get(i);
484                                        if (i > 0) {
485                                                b.append(',');
486                                        }
487                                        String valueAsQueryToken = nextValueOr.getValueAsQueryToken(theCtx);
488                                        b.append(UrlUtil.escapeUrlParam(valueAsQueryToken));
489                                }
490                        }
491
492                } // for keys
493
494                SortSpec sort = getSort();
495                boolean first = true;
496                while (sort != null) {
497
498                        if (isNotBlank(sort.getParamName())) {
499                                if (first) {
500                                        addUrlParamSeparator(b);
501                                        b.append(Constants.PARAM_SORT);
502                                        b.append('=');
503                                        first = false;
504                                } else {
505                                        b.append(',');
506                                }
507                                if (sort.getOrder() == SortOrderEnum.DESC) {
508                                        b.append('-');
509                                }
510                                b.append(sort.getParamName());
511                        }
512
513                        Validate.isTrue(sort != sort.getChain()); // just in case, shouldn't happen
514                        sort = sort.getChain();
515                }
516
517                if (hasIncludes()) {
518                        addUrlIncludeParams(b, Constants.PARAM_INCLUDE, getIncludes());
519                }
520                addUrlIncludeParams(b, Constants.PARAM_REVINCLUDE, getRevIncludes());
521
522                if (getLastUpdated() != null) {
523                        DateParam lb = getLastUpdated().getLowerBound();
524                        DateParam ub = getLastUpdated().getUpperBound();
525
526                        if (isNotEqualsComparator(lb, ub)) {
527                                addLastUpdateParam(b, NOT_EQUAL, getLastUpdated().getLowerBound());
528                        } else {
529                                addLastUpdateParam(b, GREATERTHAN_OR_EQUALS, lb);
530                                addLastUpdateParam(b, LESSTHAN_OR_EQUALS, ub);
531                        }
532                }
533
534                if (getCount() != null) {
535                        addUrlParamSeparator(b);
536                        b.append(Constants.PARAM_COUNT);
537                        b.append('=');
538                        b.append(getCount());
539                }
540
541                if (getOffset() != null) {
542                        addUrlParamSeparator(b);
543                        b.append(Constants.PARAM_OFFSET);
544                        b.append('=');
545                        b.append(getOffset());
546                }
547
548                // Summary mode (_summary)
549                if (getSummaryMode() != null) {
550                        addUrlParamSeparator(b);
551                        b.append(Constants.PARAM_SUMMARY);
552                        b.append('=');
553                        b.append(getSummaryMode().getCode());
554                }
555
556                // Search count mode (_total)
557                if (getSearchTotalMode() != null) {
558                        addUrlParamSeparator(b);
559                        b.append(Constants.PARAM_SEARCH_TOTAL_MODE);
560                        b.append('=');
561                        b.append(getSearchTotalMode().getCode());
562                }
563
564                //Contained mode
565                //For some reason, instead of null here, we default to false. That said, ommitting it is identical to setting it to false.
566                if (getSearchContainedMode() != SearchContainedModeEnum.FALSE) {
567                        addUrlParamSeparator(b);
568                        b.append(Constants.PARAM_CONTAINED);
569                        b.append("=");
570                        b.append(getSearchContainedMode().getCode());
571                }
572
573                if (b.length() == 0) {
574                        b.append('?');
575                }
576
577                return b.toString();
578        }
579
580        private boolean isNotEqualsComparator(DateParam theLowerBound, DateParam theUpperBound) {
581                return theLowerBound != null && theUpperBound != null && theLowerBound.getPrefix().equals(NOT_EQUAL) && theUpperBound.getPrefix().equals(NOT_EQUAL);
582        }
583
584        /**
585         * @since 5.5.0
586         */
587        public boolean hasIncludes() {
588                return myIncludes != null && !myIncludes.isEmpty();
589        }
590
591        @Override
592        public String toString() {
593                ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
594                if (!isEmpty()) {
595                        b.append("params", mySearchParameterMap);
596                }
597                if (!getIncludes().isEmpty()) {
598                        b.append("includes", getIncludes());
599                }
600                return b.toString();
601        }
602
603
604        public void clean() {
605                for (Map.Entry<String, List<List<IQueryParameterType>>> nextParamEntry : this.entrySet()) {
606                        String nextParamName = nextParamEntry.getKey();
607                        List<List<IQueryParameterType>> andOrParams = nextParamEntry.getValue();
608                        cleanParameter(nextParamName, andOrParams);
609                }
610        }
611
612        /*
613         * Given a particular named parameter, e.g. `name`, iterate over AndOrParams and remove any which are empty.
614         */
615        private void cleanParameter(String theParamName, List<List<IQueryParameterType>> theAndOrParams) {
616                theAndOrParams
617                        .forEach(
618                                orList -> {
619                                        List<IQueryParameterType> emptyParameters = orList.stream()
620                                                .filter(nextOr -> nextOr.getMissing() == null)
621                                                .filter(nextOr -> nextOr instanceof QuantityParam)
622                                                .filter(nextOr -> isBlank(((QuantityParam) nextOr).getValueAsString()))
623                                                .collect(Collectors.toList());
624
625                                        ourLog.debug("Ignoring empty parameter: {}", theParamName);
626                                        orList.removeAll(emptyParameters);
627                                }
628                        );
629                theAndOrParams.removeIf(List::isEmpty);
630        }
631
632        public QuantityParam getNearDistanceParam() {
633                return myNearDistanceParam;
634        }
635
636        public void setNearDistanceParam(QuantityParam theQuantityParam) {
637                myNearDistanceParam = theQuantityParam;
638        }
639
640        public boolean isWantOnlyCount() {
641                return SummaryEnum.COUNT.equals(getSummaryMode()) || INTEGER_0.equals(getCount());
642        }
643
644        public boolean isDeleteExpunge() {
645                return myDeleteExpunge;
646        }
647
648        public SearchParameterMap setDeleteExpunge(boolean theDeleteExpunge) {
649                myDeleteExpunge = theDeleteExpunge;
650                return this;
651        }
652
653        public List<List<IQueryParameterType>> get(String theName) {
654                return mySearchParameterMap.get(theName);
655        }
656
657        public void put(String theName, List<List<IQueryParameterType>> theParams) {
658                mySearchParameterMap.put(theName, theParams);
659        }
660
661        public boolean containsKey(String theName) {
662                return mySearchParameterMap.containsKey(theName);
663        }
664
665        public Set<String> keySet() {
666                return mySearchParameterMap.keySet();
667        }
668
669        public boolean isEmpty() {
670                return mySearchParameterMap.isEmpty();
671        }
672
673        // Wrapper methods
674
675        public Set<Map.Entry<String, List<List<IQueryParameterType>>>> entrySet() {
676                return mySearchParameterMap.entrySet();
677        }
678
679        public List<List<IQueryParameterType>> remove(String theName) {
680                return mySearchParameterMap.remove(theName);
681        }
682
683        /**
684         * Variant of removeByNameAndModifier for unmodified params.
685         *
686         * @param theName the query parameter key
687         * @return an And/Or List of Query Parameters matching the name with no modifier.
688         */
689        public List<List<IQueryParameterType>> removeByNameUnmodified(String theName) {
690                return this.removeByNameAndModifier(theName, "");
691        }
692
693        /**
694         * Given a search parameter name and modifier (e.g. :text),
695         * get and remove all Search Parameters matching this name and modifier
696         *
697         * @param theName     the query parameter key
698         * @param theModifier the qualifier you want to remove - nullable for unmodified params.
699         * @return an And/Or List of Query Parameters matching the qualifier.
700         */
701        public List<List<IQueryParameterType>> removeByNameAndModifier(String theName, String theModifier) {
702                theModifier = StringUtils.defaultString(theModifier, "");
703
704                List<List<IQueryParameterType>> remainderParameters = new ArrayList<>();
705                List<List<IQueryParameterType>> matchingParameters = new ArrayList<>();
706
707                // pull all of them out, partition by match against the qualifier
708                List<List<IQueryParameterType>> andList = mySearchParameterMap.remove(theName);
709                if (andList != null) {
710                        for (List<IQueryParameterType> orList : andList) {
711                                if (!orList.isEmpty() &&
712                                        StringUtils.defaultString(orList.get(0).getQueryParameterQualifier(), "")
713                                                .equals(theModifier)) {
714                                        matchingParameters.add(orList);
715                                } else {
716                                        remainderParameters.add(orList);
717                                }
718                        }
719                }
720
721                // put the unmatched back in.
722                if (!remainderParameters.isEmpty()) {
723                        mySearchParameterMap.put(theName, remainderParameters);
724                }
725                return matchingParameters;
726
727        }
728
729        public List<List<IQueryParameterType>> removeByNameAndModifier(String theName, @Nonnull TokenParamModifier theModifier) {
730                return removeByNameAndModifier(theName, theModifier.getValue());
731        }
732
733        /**
734         * For each search parameter in the map, extract any which have the given qualifier.
735         * e.g. Take the url: Observation?code:text=abc&code=123&code:text=def&reason:text=somereason
736         * <p>
737         * If we call this function with `:text`, it will return a map that looks like:
738         * <p>
739         * code -> [[code:text=abc], [code:text=def]]
740         * reason -> [[reason:text=somereason]]
741         * <p>
742         * and the remaining search parameters in the map will be:
743         * <p>
744         * code -> [[code=123]]
745         *
746         * @param theQualifier
747         * @return
748         */
749        public Map<String, List<List<IQueryParameterType>>> removeByQualifier(String theQualifier) {
750
751                Map<String, List<List<IQueryParameterType>>> retVal = new HashMap<>();
752                Set<String> parameterNames = mySearchParameterMap.keySet();
753                for (String parameterName : parameterNames) {
754                        List<List<IQueryParameterType>> paramsWithQualifier = removeByNameAndModifier(parameterName, theQualifier);
755                        retVal.put(parameterName, paramsWithQualifier);
756                }
757
758                return retVal;
759
760        }
761
762        public Map<String, List<List<IQueryParameterType>>> removeByQualifier(@Nonnull TokenParamModifier theModifier) {
763                return removeByQualifier(theModifier.getValue());
764        }
765
766        public int size() {
767                return mySearchParameterMap.size();
768        }
769
770        public SearchContainedModeEnum getSearchContainedMode() {
771                return mySearchContainedMode;
772        }
773
774        public void setSearchContainedMode(SearchContainedModeEnum theSearchContainedMode) {
775                if (theSearchContainedMode == null) {
776                        mySearchContainedMode = SearchContainedModeEnum.FALSE;
777                } else {
778                        this.mySearchContainedMode = theSearchContainedMode;
779                }
780        }
781
782        /**
783         * Returns true if {@link #getOffset()} and {@link #getCount()} both return a non null response
784         *
785         * @since 5.5.0
786         */
787        public boolean isOffsetQuery() {
788                return getOffset() != null && getCount() != null;
789        }
790
791        public enum EverythingModeEnum {
792                /*
793                 * Don't reorder! We rely on the ordinals
794                 */
795                ENCOUNTER_INSTANCE(false, true, true),
796                ENCOUNTER_TYPE(false, true, false),
797                PATIENT_INSTANCE(true, false, true),
798                PATIENT_TYPE(true, false, false);
799
800                private final boolean myEncounter;
801
802                private final boolean myInstance;
803
804                private final boolean myPatient;
805
806                EverythingModeEnum(boolean thePatient, boolean theEncounter, boolean theInstance) {
807                        assert thePatient ^ theEncounter;
808                        myPatient = thePatient;
809                        myEncounter = theEncounter;
810                        myInstance = theInstance;
811                }
812
813                public boolean isEncounter() {
814                        return myEncounter;
815                }
816
817                public boolean isInstance() {
818                        return myInstance;
819                }
820
821                public boolean isPatient() {
822                        return myPatient;
823                }
824        }
825
826        static int compare(FhirContext theCtx, IQueryParameterType theO1, IQueryParameterType theO2) {
827                CompareToBuilder b = new CompareToBuilder();
828                b.append(theO1.getMissing(), theO2.getMissing());
829                b.append(theO1.getQueryParameterQualifier(), theO2.getQueryParameterQualifier());
830                if (b.toComparison() == 0) {
831                        b.append(theO1.getValueAsQueryToken(theCtx), theO2.getValueAsQueryToken(theCtx));
832                }
833
834                return b.toComparison();
835        }
836
837        public static SearchParameterMap newSynchronous() {
838                SearchParameterMap retVal = new SearchParameterMap();
839                retVal.setLoadSynchronous(true);
840                return retVal;
841        }
842
843        public static SearchParameterMap newSynchronous(String theName, IQueryParameterType theParam) {
844                SearchParameterMap retVal = new SearchParameterMap();
845                retVal.setLoadSynchronous(true);
846                retVal.add(theName, theParam);
847                return retVal;
848        }
849
850        public static class IncludeComparator implements Comparator<Include> {
851
852                @Override
853                public int compare(Include theO1, Include theO2) {
854                        int retVal = StringUtils.compare(theO1.getParamType(), theO2.getParamType());
855                        if (retVal == 0) {
856                                retVal = StringUtils.compare(theO1.getParamName(), theO2.getParamName());
857                        }
858                        if (retVal == 0) {
859                                retVal = StringUtils.compare(theO1.getParamTargetType(), theO2.getParamTargetType());
860                        }
861                        return retVal;
862                }
863
864        }
865
866        public static class QueryParameterOrComparator implements Comparator<List<IQueryParameterType>> {
867                private final FhirContext myCtx;
868
869                QueryParameterOrComparator(FhirContext theCtx) {
870                        myCtx = theCtx;
871                }
872
873                @Override
874                public int compare(List<IQueryParameterType> theO1, List<IQueryParameterType> theO2) {
875                        // These lists will never be empty
876                        return SearchParameterMap.compare(myCtx, theO1.get(0), theO2.get(0));
877                }
878
879        }
880
881        public static class QueryParameterTypeComparator implements Comparator<IQueryParameterType> {
882
883                private final FhirContext myCtx;
884
885                QueryParameterTypeComparator(FhirContext theCtx) {
886                        myCtx = theCtx;
887                }
888
889                @Override
890                public int compare(IQueryParameterType theO1, IQueryParameterType theO2) {
891                        return SearchParameterMap.compare(myCtx, theO1, theO2);
892                }
893
894        }
895
896
897}