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