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