001/* 002 * #%L 003 * HAPI FHIR JPA - Search Parameters 004 * %% 005 * Copyright (C) 2014 - 2024 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 * If set, tells the server to load these results synchronously, and not to load 358 * more than X results 359 */ 360 public boolean isLoadSynchronous() { 361 return myLoadSynchronous; 362 } 363 364 /** 365 * If set, tells the server to load these results synchronously, and not to load 366 * more than X results 367 */ 368 public SearchParameterMap setLoadSynchronous(boolean theLoadSynchronous) { 369 myLoadSynchronous = theLoadSynchronous; 370 return this; 371 } 372 373 /** 374 * If set, tells the server to use an Elasticsearch query to generate a list of 375 * Resource IDs for the LastN operation 376 */ 377 public boolean isLastN() { 378 return myLastN; 379 } 380 381 /** 382 * If set, tells the server to use an Elasticsearch query to generate a list of 383 * Resource IDs for the LastN operation 384 */ 385 public SearchParameterMap setLastN(boolean theLastN) { 386 myLastN = theLastN; 387 return this; 388 } 389 390 /** 391 * If set, tells the server the maximum number of observations to return for each 392 * observation code in the result set of a lastn operation 393 */ 394 public Integer getLastNMax() { 395 return myLastNMax; 396 } 397 398 /** 399 * If set, tells the server the maximum number of observations to return for each 400 * observation code in the result set of a lastn operation 401 */ 402 public SearchParameterMap setLastNMax(Integer theLastNMax) { 403 myLastNMax = theLastNMax; 404 return this; 405 } 406 407 /** 408 * This method creates a URL query string representation of the parameters in this 409 * object, excluding the part before the parameters, e.g. 410 * <p> 411 * <code>?name=smith&_sort=Patient:family</code> 412 * </p> 413 * <p> 414 * This method <b>excludes</b> the <code>_count</code> parameter, 415 * as it doesn't affect the substance of the results returned 416 * </p> 417 */ 418 public String toNormalizedQueryString(FhirContext theCtx) { 419 StringBuilder b = new StringBuilder(); 420 421 ArrayList<String> keys = new ArrayList<>(keySet()); 422 Collections.sort(keys); 423 for (String nextKey : keys) { 424 425 List<List<IQueryParameterType>> nextValuesAndsIn = get(nextKey); 426 List<List<IQueryParameterType>> nextValuesAndsOut = new ArrayList<>(); 427 428 for (List<? extends IQueryParameterType> nextValuesAndIn : nextValuesAndsIn) { 429 430 List<IQueryParameterType> nextValuesOrsOut = new ArrayList<>(); 431 432 nextValuesOrsOut.addAll(nextValuesAndIn); 433 434 nextValuesOrsOut.sort(new QueryParameterTypeComparator(theCtx)); 435 436 if (nextValuesOrsOut.size() > 0) { 437 nextValuesAndsOut.add(nextValuesOrsOut); 438 } 439 } // for AND 440 441 nextValuesAndsOut.sort(new QueryParameterOrComparator(theCtx)); 442 443 for (List<IQueryParameterType> nextValuesAnd : nextValuesAndsOut) { 444 addUrlParamSeparator(b); 445 IQueryParameterType firstValue = nextValuesAnd.get(0); 446 b.append(UrlUtil.escapeUrlParam(nextKey)); 447 448 if (firstValue.getMissing() != null) { 449 b.append(Constants.PARAMQUALIFIER_MISSING); 450 b.append('='); 451 if (firstValue.getMissing()) { 452 b.append(Constants.PARAMQUALIFIER_MISSING_TRUE); 453 } else { 454 b.append(Constants.PARAMQUALIFIER_MISSING_FALSE); 455 } 456 continue; 457 } 458 459 if (isNotBlank(firstValue.getQueryParameterQualifier())) { 460 b.append(firstValue.getQueryParameterQualifier()); 461 } 462 463 b.append('='); 464 465 for (int i = 0; i < nextValuesAnd.size(); i++) { 466 IQueryParameterType nextValueOr = nextValuesAnd.get(i); 467 if (i > 0) { 468 b.append(','); 469 } 470 String valueAsQueryToken = nextValueOr.getValueAsQueryToken(theCtx); 471 valueAsQueryToken = defaultString(valueAsQueryToken); 472 b.append(UrlUtil.escapeUrlParam(valueAsQueryToken)); 473 } 474 } 475 } // for keys 476 477 SortSpec sort = getSort(); 478 boolean first = true; 479 while (sort != null) { 480 481 if (isNotBlank(sort.getParamName())) { 482 if (first) { 483 addUrlParamSeparator(b); 484 b.append(Constants.PARAM_SORT); 485 b.append('='); 486 first = false; 487 } else { 488 b.append(','); 489 } 490 if (sort.getOrder() == SortOrderEnum.DESC) { 491 b.append('-'); 492 } 493 b.append(sort.getParamName()); 494 } 495 496 Validate.isTrue(sort != sort.getChain()); // just in case, shouldn't happen 497 sort = sort.getChain(); 498 } 499 500 if (hasIncludes()) { 501 addUrlIncludeParams(b, Constants.PARAM_INCLUDE, getIncludes()); 502 } 503 addUrlIncludeParams(b, Constants.PARAM_REVINCLUDE, getRevIncludes()); 504 505 if (getLastUpdated() != null) { 506 DateParam lb = getLastUpdated().getLowerBound(); 507 DateParam ub = getLastUpdated().getUpperBound(); 508 509 if (isNotEqualsComparator(lb, ub)) { 510 addLastUpdateParam(b, NOT_EQUAL, getLastUpdated().getLowerBound()); 511 } else { 512 addLastUpdateParam(b, GREATERTHAN_OR_EQUALS, lb); 513 addLastUpdateParam(b, LESSTHAN_OR_EQUALS, ub); 514 } 515 } 516 517 if (getCount() != null) { 518 addUrlParamSeparator(b); 519 b.append(Constants.PARAM_COUNT); 520 b.append('='); 521 b.append(getCount()); 522 } 523 524 if (getOffset() != null) { 525 addUrlParamSeparator(b); 526 b.append(Constants.PARAM_OFFSET); 527 b.append('='); 528 b.append(getOffset()); 529 } 530 531 // Summary mode (_summary) 532 if (getSummaryMode() != null) { 533 addUrlParamSeparator(b); 534 b.append(Constants.PARAM_SUMMARY); 535 b.append('='); 536 b.append(getSummaryMode().getCode()); 537 } 538 539 // Search count mode (_total) 540 if (getSearchTotalMode() != null) { 541 addUrlParamSeparator(b); 542 b.append(Constants.PARAM_SEARCH_TOTAL_MODE); 543 b.append('='); 544 b.append(getSearchTotalMode().getCode()); 545 } 546 547 // Contained mode 548 // For some reason, instead of null here, we default to false. That said, ommitting it is identical to setting 549 // it to false. 550 if (getSearchContainedMode() != SearchContainedModeEnum.FALSE) { 551 addUrlParamSeparator(b); 552 b.append(Constants.PARAM_CONTAINED); 553 b.append("="); 554 b.append(getSearchContainedMode().getCode()); 555 } 556 557 if (b.length() == 0) { 558 b.append('?'); 559 } 560 561 return b.toString(); 562 } 563 564 private boolean isNotEqualsComparator(DateParam theLowerBound, DateParam theUpperBound) { 565 return theLowerBound != null 566 && theUpperBound != null 567 && theLowerBound.getPrefix().equals(NOT_EQUAL) 568 && theUpperBound.getPrefix().equals(NOT_EQUAL); 569 } 570 571 /** 572 * @since 5.5.0 573 */ 574 public boolean hasIncludes() { 575 return myIncludes != null && !myIncludes.isEmpty(); 576 } 577 578 /** 579 * @since 6.2.0 580 */ 581 public boolean hasRevIncludes() { 582 return myRevIncludes != null && !myRevIncludes.isEmpty(); 583 } 584 585 @Override 586 public String toString() { 587 ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); 588 if (!isEmpty()) { 589 b.append("params", mySearchParameterMap); 590 } 591 if (!getIncludes().isEmpty()) { 592 b.append("includes", getIncludes()); 593 } 594 return b.toString(); 595 } 596 597 public void clean() { 598 for (Map.Entry<String, List<List<IQueryParameterType>>> nextParamEntry : this.entrySet()) { 599 String nextParamName = nextParamEntry.getKey(); 600 List<List<IQueryParameterType>> andOrParams = nextParamEntry.getValue(); 601 cleanParameter(nextParamName, andOrParams); 602 } 603 } 604 605 /* 606 * Given a particular named parameter, e.g. `name`, iterate over AndOrParams and remove any which are empty. 607 */ 608 private void cleanParameter(String theParamName, List<List<IQueryParameterType>> theAndOrParams) { 609 theAndOrParams.forEach(orList -> { 610 List<IQueryParameterType> emptyParameters = orList.stream() 611 .filter(nextOr -> nextOr.getMissing() == null) 612 .filter(nextOr -> nextOr instanceof QuantityParam) 613 .filter(nextOr -> isBlank(((QuantityParam) nextOr).getValueAsString())) 614 .collect(Collectors.toList()); 615 616 ourLog.debug("Ignoring empty parameter: {}", theParamName); 617 orList.removeAll(emptyParameters); 618 }); 619 theAndOrParams.removeIf(List::isEmpty); 620 } 621 622 public QuantityParam getNearDistanceParam() { 623 return myNearDistanceParam; 624 } 625 626 public void setNearDistanceParam(QuantityParam theQuantityParam) { 627 myNearDistanceParam = theQuantityParam; 628 } 629 630 public boolean isWantOnlyCount() { 631 return SummaryEnum.COUNT.equals(getSummaryMode()) || INTEGER_0.equals(getCount()); 632 } 633 634 public boolean isDeleteExpunge() { 635 return myDeleteExpunge; 636 } 637 638 public SearchParameterMap setDeleteExpunge(boolean theDeleteExpunge) { 639 myDeleteExpunge = theDeleteExpunge; 640 return this; 641 } 642 643 public List<List<IQueryParameterType>> get(String theName) { 644 return mySearchParameterMap.get(theName); 645 } 646 647 public void put(String theName, List<List<IQueryParameterType>> theParams) { 648 mySearchParameterMap.put(theName, theParams); 649 } 650 651 public boolean containsKey(String theName) { 652 return mySearchParameterMap.containsKey(theName); 653 } 654 655 public Set<String> keySet() { 656 return mySearchParameterMap.keySet(); 657 } 658 659 public boolean isEmpty() { 660 return mySearchParameterMap.isEmpty(); 661 } 662 663 // Wrapper methods 664 665 public Set<Map.Entry<String, List<List<IQueryParameterType>>>> entrySet() { 666 return mySearchParameterMap.entrySet(); 667 } 668 669 public List<List<IQueryParameterType>> remove(String theName) { 670 return mySearchParameterMap.remove(theName); 671 } 672 673 /** 674 * Variant of removeByNameAndModifier for unmodified params. 675 * 676 * @param theName the query parameter key 677 * @return an And/Or List of Query Parameters matching the name with no modifier. 678 */ 679 public List<List<IQueryParameterType>> removeByNameUnmodified(String theName) { 680 return this.removeByNameAndModifier(theName, ""); 681 } 682 683 /** 684 * Given a search parameter name and modifier (e.g. :text), 685 * get and remove all Search Parameters matching this name and modifier 686 * 687 * @param theName the query parameter key 688 * @param theModifier the qualifier you want to remove - nullable for unmodified params. 689 * @return an And/Or List of Query Parameters matching the qualifier. 690 */ 691 public List<List<IQueryParameterType>> removeByNameAndModifier(String theName, String theModifier) { 692 theModifier = StringUtils.defaultString(theModifier, ""); 693 694 List<List<IQueryParameterType>> remainderParameters = new ArrayList<>(); 695 List<List<IQueryParameterType>> matchingParameters = new ArrayList<>(); 696 697 // pull all of them out, partition by match against the qualifier 698 List<List<IQueryParameterType>> andList = mySearchParameterMap.remove(theName); 699 if (andList != null) { 700 for (List<IQueryParameterType> orList : andList) { 701 if (!orList.isEmpty() 702 && StringUtils.defaultString(orList.get(0).getQueryParameterQualifier(), "") 703 .equals(theModifier)) { 704 matchingParameters.add(orList); 705 } else { 706 remainderParameters.add(orList); 707 } 708 } 709 } 710 711 // put the unmatched back in. 712 if (!remainderParameters.isEmpty()) { 713 mySearchParameterMap.put(theName, remainderParameters); 714 } 715 return matchingParameters; 716 } 717 718 public List<List<IQueryParameterType>> removeByNameAndModifier( 719 String theName, @Nonnull TokenParamModifier theModifier) { 720 return removeByNameAndModifier(theName, theModifier.getValue()); 721 } 722 723 /** 724 * For each search parameter in the map, extract any which have the given qualifier. 725 * e.g. Take the url: Observation?code:text=abc&code=123&code:text=def&reason:text=somereason 726 * <p> 727 * If we call this function with `:text`, it will return a map that looks like: 728 * <p> 729 * code -> [[code:text=abc], [code:text=def]] 730 * reason -> [[reason:text=somereason]] 731 * <p> 732 * and the remaining search parameters in the map will be: 733 * <p> 734 * code -> [[code=123]] 735 * 736 * @param theQualifier 737 * @return 738 */ 739 public Map<String, List<List<IQueryParameterType>>> removeByQualifier(String theQualifier) { 740 741 Map<String, List<List<IQueryParameterType>>> retVal = new HashMap<>(); 742 Set<String> parameterNames = mySearchParameterMap.keySet(); 743 for (String parameterName : parameterNames) { 744 List<List<IQueryParameterType>> paramsWithQualifier = removeByNameAndModifier(parameterName, theQualifier); 745 retVal.put(parameterName, paramsWithQualifier); 746 } 747 748 return retVal; 749 } 750 751 public Map<String, List<List<IQueryParameterType>>> removeByQualifier(@Nonnull TokenParamModifier theModifier) { 752 return removeByQualifier(theModifier.getValue()); 753 } 754 755 public int size() { 756 return mySearchParameterMap.size(); 757 } 758 759 public SearchContainedModeEnum getSearchContainedMode() { 760 return mySearchContainedMode; 761 } 762 763 public void setSearchContainedMode(SearchContainedModeEnum theSearchContainedMode) { 764 if (theSearchContainedMode == null) { 765 mySearchContainedMode = SearchContainedModeEnum.FALSE; 766 } else { 767 this.mySearchContainedMode = theSearchContainedMode; 768 } 769 } 770 771 /** 772 * Returns true if {@link #getOffset()} and {@link #getCount()} both return a non null response 773 * 774 * @since 5.5.0 775 */ 776 public boolean isOffsetQuery() { 777 return getOffset() != null && getCount() != null; 778 } 779 780 public enum EverythingModeEnum { 781 /* 782 * Don't reorder! We rely on the ordinals 783 */ 784 ENCOUNTER_INSTANCE(false, true, true), 785 ENCOUNTER_TYPE(false, true, false), 786 PATIENT_INSTANCE(true, false, true), 787 PATIENT_TYPE(true, false, false); 788 789 private final boolean myEncounter; 790 791 private final boolean myInstance; 792 793 private final boolean myPatient; 794 795 EverythingModeEnum(boolean thePatient, boolean theEncounter, boolean theInstance) { 796 assert thePatient ^ theEncounter; 797 myPatient = thePatient; 798 myEncounter = theEncounter; 799 myInstance = theInstance; 800 } 801 802 public boolean isEncounter() { 803 return myEncounter; 804 } 805 806 public boolean isInstance() { 807 return myInstance; 808 } 809 810 public boolean isPatient() { 811 return myPatient; 812 } 813 } 814 815 static int compare(FhirContext theCtx, IQueryParameterType theO1, IQueryParameterType theO2) { 816 CompareToBuilder b = new CompareToBuilder(); 817 b.append(theO1.getMissing(), theO2.getMissing()); 818 b.append(theO1.getQueryParameterQualifier(), theO2.getQueryParameterQualifier()); 819 if (b.toComparison() == 0) { 820 b.append(theO1.getValueAsQueryToken(theCtx), theO2.getValueAsQueryToken(theCtx)); 821 } 822 823 return b.toComparison(); 824 } 825 826 public static SearchParameterMap newSynchronous() { 827 SearchParameterMap retVal = new SearchParameterMap(); 828 retVal.setLoadSynchronous(true); 829 return retVal; 830 } 831 832 public static SearchParameterMap newSynchronous(String theName, IQueryParameterType theParam) { 833 SearchParameterMap retVal = new SearchParameterMap(); 834 retVal.setLoadSynchronous(true); 835 retVal.add(theName, theParam); 836 return retVal; 837 } 838 839 public static class IncludeComparator implements Comparator<Include> { 840 841 @Override 842 public int compare(Include theO1, Include theO2) { 843 int retVal = StringUtils.compare(theO1.getParamType(), theO2.getParamType()); 844 if (retVal == 0) { 845 retVal = StringUtils.compare(theO1.getParamName(), theO2.getParamName()); 846 } 847 if (retVal == 0) { 848 retVal = StringUtils.compare(theO1.getParamTargetType(), theO2.getParamTargetType()); 849 } 850 return retVal; 851 } 852 } 853 854 public static class QueryParameterOrComparator implements Comparator<List<IQueryParameterType>> { 855 private final FhirContext myCtx; 856 857 QueryParameterOrComparator(FhirContext theCtx) { 858 myCtx = theCtx; 859 } 860 861 @Override 862 public int compare(List<IQueryParameterType> theO1, List<IQueryParameterType> theO2) { 863 // These lists will never be empty 864 return SearchParameterMap.compare(myCtx, theO1.get(0), theO2.get(0)); 865 } 866 } 867 868 public static class QueryParameterTypeComparator implements Comparator<IQueryParameterType> { 869 870 private final FhirContext myCtx; 871 872 QueryParameterTypeComparator(FhirContext theCtx) { 873 myCtx = theCtx; 874 } 875 876 @Override 877 public int compare(IQueryParameterType theO1, IQueryParameterType theO2) { 878 return SearchParameterMap.compare(myCtx, theO1, theO2); 879 } 880 } 881 882 public List<SortSpec> getAllChainsInOrder() { 883 final List<SortSpec> allChainsInOrder = new ArrayList<>(); 884 for (SortSpec sortSpec = getSort(); sortSpec != null; sortSpec = sortSpec.getChain()) { 885 allChainsInOrder.add(sortSpec); 886 } 887 888 return Collections.unmodifiableList(allChainsInOrder); 889 } 890}