001/* 002 * #%L 003 * HAPI FHIR - Core Library 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.rest.param; 021 022import ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.model.api.IQueryParameterAnd; 025import ca.uhn.fhir.model.api.TemporalPrecisionEnum; 026import ca.uhn.fhir.parser.DataFormatException; 027import ca.uhn.fhir.rest.api.QualifiedParamList; 028import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 029import ca.uhn.fhir.util.DateUtils; 030import jakarta.annotation.Nonnull; 031import org.apache.commons.lang3.Validate; 032import org.hl7.fhir.instance.model.api.IPrimitiveType; 033 034import java.util.ArrayList; 035import java.util.Date; 036import java.util.List; 037import java.util.Objects; 038 039import static ca.uhn.fhir.rest.param.ParamPrefixEnum.EQUAL; 040import static ca.uhn.fhir.rest.param.ParamPrefixEnum.GREATERTHAN_OR_EQUALS; 041import static ca.uhn.fhir.rest.param.ParamPrefixEnum.LESSTHAN_OR_EQUALS; 042import static java.lang.String.format; 043import static org.apache.commons.lang3.StringUtils.isNotBlank; 044 045@SuppressWarnings("UnusedReturnValue") 046public class DateRangeParam implements IQueryParameterAnd<DateParam> { 047 048 private static final long serialVersionUID = 1L; 049 050 private DateParam myLowerBound; 051 private DateParam myUpperBound; 052 053 /** 054 * Basic constructor. Values must be supplied by calling {@link #setLowerBound(DateParam)} and 055 * {@link #setUpperBound(DateParam)} 056 */ 057 public DateRangeParam() { 058 super(); 059 } 060 061 /** 062 * Copy constructor. 063 */ 064 @SuppressWarnings("CopyConstructorMissesField") 065 public DateRangeParam(DateRangeParam theDateRangeParam) { 066 super(); 067 Validate.notNull(theDateRangeParam); 068 setLowerBound(theDateRangeParam.getLowerBound()); 069 setUpperBound(theDateRangeParam.getUpperBound()); 070 } 071 072 /** 073 * Constructor which takes two Dates representing the lower and upper bounds of the range (inclusive on both ends) 074 * 075 * @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g. 076 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or 077 * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 078 * @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g. 079 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or 080 * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 081 */ 082 public DateRangeParam(Date theLowerBound, Date theUpperBound) { 083 this(); 084 setRangeFromDatesInclusive(theLowerBound, theUpperBound); 085 } 086 087 /** 088 * Sets the range from a single date param. If theDateParam has no qualifier, treats it as the lower and upper bound 089 * (e.g. 2011-01-02 would match any time on that day). If theDateParam has a qualifier, treats it as either the lower 090 * or upper bound, with no opposite bound. 091 */ 092 public DateRangeParam(DateParam theDateParam) { 093 this(); 094 if (theDateParam == null) { 095 throw new NullPointerException(Msg.code(1919) + "theDateParam can not be null"); 096 } 097 if (theDateParam.isEmpty()) { 098 throw new IllegalArgumentException(Msg.code(1920) + "theDateParam can not be empty"); 099 } 100 if (theDateParam.getPrefix() == null) { 101 setRangeFromDatesInclusive(theDateParam.getValueAsString(), theDateParam.getValueAsString()); 102 } else { 103 switch (theDateParam.getPrefix()) { 104 case NOT_EQUAL: 105 case EQUAL: 106 setRangeFromDatesInclusive(theDateParam.getValueAsString(), theDateParam.getValueAsString()); 107 break; 108 case STARTS_AFTER: 109 case GREATERTHAN: 110 case GREATERTHAN_OR_EQUALS: 111 if (theDateParam.getPrecision().ordinal() <= TemporalPrecisionEnum.MONTH.ordinal()) { 112 theDateParam.setValueAsString(DateUtils.getCompletedDate(theDateParam.getValueAsString()) 113 .getRight()); 114 } 115 validateAndSet(theDateParam, null); 116 break; 117 case ENDS_BEFORE: 118 case LESSTHAN: 119 case LESSTHAN_OR_EQUALS: 120 if (theDateParam.getPrecision().ordinal() <= TemporalPrecisionEnum.MONTH.ordinal()) { 121 theDateParam.setValueAsString(DateUtils.getCompletedDate(theDateParam.getValueAsString()) 122 .getLeft()); 123 } 124 validateAndSet(null, theDateParam); 125 break; 126 default: 127 // Should not happen 128 throw new InvalidRequestException(Msg.code(1921) + "Invalid comparator for date range parameter:" 129 + theDateParam.getPrefix() + ". This is a bug."); 130 } 131 } 132 } 133 134 /** 135 * Constructor which takes two Dates representing the lower and upper bounds of the range (inclusive on both ends) 136 * 137 * @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g. 138 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or 139 * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 140 * @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g. 141 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or 142 * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 143 */ 144 public DateRangeParam(DateParam theLowerBound, DateParam theUpperBound) { 145 this(); 146 setRangeFromDatesInclusive(theLowerBound, theUpperBound); 147 } 148 149 /** 150 * Constructor which takes two Dates representing the lower and upper bounds of the range (inclusive on both ends) 151 * 152 * @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g. 153 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or 154 * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 155 * @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g. 156 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or 157 * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 158 */ 159 public DateRangeParam(IPrimitiveType<Date> theLowerBound, IPrimitiveType<Date> theUpperBound) { 160 this(); 161 setRangeFromDatesInclusive(theLowerBound, theUpperBound); 162 } 163 164 /** 165 * Constructor which takes two strings representing the lower and upper bounds of the range (inclusive on both ends) 166 * 167 * @param theLowerBound An unqualified date param representing the lower date bound (optionally may include time), e.g. 168 * "2011-02-22" or "2011-02-22T13:12:00Z". Either theLowerBound or theUpperBound may both be populated, or 169 * one may be null, but it is not valid for both to be null. 170 * @param theUpperBound An unqualified date param representing the upper date bound (optionally may include time), e.g. 171 * "2011-02-22" or "2011-02-22T13:12:00Z". Either theLowerBound or theUpperBound may both be populated, or 172 * one may be null, but it is not valid for both to be null. 173 */ 174 public DateRangeParam(String theLowerBound, String theUpperBound) { 175 this(); 176 setRangeFromDatesInclusive(theLowerBound, theUpperBound); 177 } 178 179 private void addParam(DateParam theParsed) throws InvalidRequestException { 180 if (theParsed.getPrefix() == null) { 181 theParsed.setPrefix(EQUAL); 182 } 183 184 switch (theParsed.getPrefix()) { 185 case NOT_EQUAL: 186 case EQUAL: 187 if (myLowerBound != null || myUpperBound != null) { 188 throw new InvalidRequestException(Msg.code(1922) 189 + "Can not have multiple date range parameters for the same param without a qualifier"); 190 } 191 if (theParsed.getMissing() != null) { 192 myLowerBound = theParsed; 193 myUpperBound = theParsed; 194 } else { 195 myLowerBound = new DateParam(theParsed.getPrefix(), theParsed.getValueAsString()); 196 myUpperBound = new DateParam(theParsed.getPrefix(), theParsed.getValueAsString()); 197 } 198 break; 199 case GREATERTHAN: 200 case GREATERTHAN_OR_EQUALS: 201 case STARTS_AFTER: 202 if (myLowerBound != null) { 203 throw new InvalidRequestException( 204 Msg.code(1923) 205 + "Can not have multiple date range parameters for the same param that specify a lower bound"); 206 } 207 myLowerBound = theParsed; 208 break; 209 case LESSTHAN: 210 case LESSTHAN_OR_EQUALS: 211 case ENDS_BEFORE: 212 if (myUpperBound != null) { 213 throw new InvalidRequestException( 214 Msg.code(1924) 215 + "Can not have multiple date range parameters for the same param that specify an upper bound"); 216 } 217 myUpperBound = theParsed; 218 break; 219 default: 220 throw new InvalidRequestException(Msg.code(1925) + "Unknown comparator: " + theParsed.getPrefix()); 221 } 222 } 223 224 @Override 225 public boolean equals(Object obj) { 226 if (obj == this) { 227 return true; 228 } 229 if (!(obj instanceof DateRangeParam)) { 230 return false; 231 } 232 DateRangeParam other = (DateRangeParam) obj; 233 return Objects.equals(myLowerBound, other.myLowerBound) && Objects.equals(myUpperBound, other.myUpperBound); 234 } 235 236 public DateParam getLowerBound() { 237 return myLowerBound; 238 } 239 240 public DateRangeParam setLowerBound(DateParam theLowerBound) { 241 validateAndSet(theLowerBound, myUpperBound); 242 return this; 243 } 244 245 /** 246 * Sets the lower bound using a string that is compliant with 247 * FHIR dateTime format (ISO-8601). 248 * <p> 249 * This lower bound is assumed to have a <code>ge</code> 250 * (greater than or equals) modifier. 251 * </p> 252 * <p> 253 * Note: An operation can take a DateRangeParam. If only a single date is provided, 254 * it will still result in a DateRangeParam where the lower and upper bounds 255 * are the same value. As such, even though the prefixes for the lower and 256 * upper bounds default to <code>ge</code> and <code>le</code> respectively, 257 * the resulting prefix is effectively <code>eq</code> where only a single 258 * date is provided - as required by the FHIR specification (i.e. "If no 259 * prefix is present, the prefix <code>eq</code> is assumed"). 260 * </p> 261 */ 262 public DateRangeParam setLowerBound(String theLowerBound) { 263 setLowerBound(new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound)); 264 return this; 265 } 266 267 /** 268 * Sets the lower bound to be greaterthan or equal to the given date 269 */ 270 public DateRangeParam setLowerBoundInclusive(Date theLowerBound) { 271 validateAndSet(new DateParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, theLowerBound), myUpperBound); 272 return this; 273 } 274 275 /** 276 * Sets the upper bound to be greaterthan or equal to the given date 277 */ 278 public DateRangeParam setUpperBoundInclusive(Date theUpperBound) { 279 validateAndSet(myLowerBound, new DateParam(ParamPrefixEnum.LESSTHAN_OR_EQUALS, theUpperBound)); 280 return this; 281 } 282 283 /** 284 * Sets the lower bound to be greaterthan to the given date 285 */ 286 public DateRangeParam setLowerBoundExclusive(Date theLowerBound) { 287 validateAndSet(new DateParam(ParamPrefixEnum.GREATERTHAN, theLowerBound), myUpperBound); 288 return this; 289 } 290 291 /** 292 * Sets the upper bound to be greaterthan to the given date 293 */ 294 public DateRangeParam setUpperBoundExclusive(Date theUpperBound) { 295 validateAndSet(myLowerBound, new DateParam(ParamPrefixEnum.LESSTHAN, theUpperBound)); 296 return this; 297 } 298 299 /** 300 * Return the current lower bound as an integer representative of the date. 301 * 302 * e.g. 2019-02-22T04:22:00-0500 -> 20120922 303 */ 304 public Integer getLowerBoundAsDateInteger() { 305 if (myLowerBound == null || myLowerBound.getValue() == null) { 306 return null; 307 } 308 int retVal = DateUtils.convertDateToDayInteger(myLowerBound.getValue()); 309 310 if (myLowerBound.getPrefix() != null) { 311 switch (myLowerBound.getPrefix()) { 312 case GREATERTHAN: 313 case STARTS_AFTER: 314 retVal += 1; 315 break; 316 case EQUAL: 317 case GREATERTHAN_OR_EQUALS: 318 case NOT_EQUAL: 319 break; 320 case LESSTHAN: 321 case APPROXIMATE: 322 case LESSTHAN_OR_EQUALS: 323 case ENDS_BEFORE: 324 throw new IllegalStateException( 325 Msg.code(1926) + "Invalid lower bound comparator: " + myLowerBound.getPrefix()); 326 } 327 } 328 return retVal; 329 } 330 331 /** 332 * Return the current upper bound as an integer representative of the date 333 * 334 * e.g. 2019-02-22T04:22:00-0500 -> 2019122 335 */ 336 public Integer getUpperBoundAsDateInteger() { 337 if (myUpperBound == null || myUpperBound.getValue() == null) { 338 return null; 339 } 340 int retVal = DateUtils.convertDateToDayInteger(myUpperBound.getValue()); 341 if (myUpperBound.getPrefix() != null) { 342 switch (myUpperBound.getPrefix()) { 343 case LESSTHAN: 344 case ENDS_BEFORE: 345 retVal -= 1; 346 break; 347 case EQUAL: 348 case LESSTHAN_OR_EQUALS: 349 case NOT_EQUAL: 350 break; 351 case GREATERTHAN_OR_EQUALS: 352 case GREATERTHAN: 353 case APPROXIMATE: 354 case STARTS_AFTER: 355 throw new IllegalStateException( 356 Msg.code(1927) + "Invalid upper bound comparator: " + myUpperBound.getPrefix()); 357 } 358 } 359 return retVal; 360 } 361 362 public Date getLowerBoundAsInstant() { 363 if (myLowerBound == null || myLowerBound.getValue() == null) { 364 return null; 365 } 366 return getLowerBoundAsInstant(myLowerBound); 367 } 368 369 @Nonnull 370 private static Date getLowerBoundAsInstant(@Nonnull DateParam theLowerBound) { 371 Date retVal = theLowerBound.getValue(); 372 if (theLowerBound.getPrecision().ordinal() <= TemporalPrecisionEnum.DAY.ordinal()) { 373 retVal = DateUtils.getLowestInstantFromDate(retVal); 374 } 375 376 if (theLowerBound.getPrefix() != null) { 377 switch (theLowerBound.getPrefix()) { 378 case GREATERTHAN: 379 case STARTS_AFTER: 380 retVal = theLowerBound.getPrecision().add(retVal, 1); 381 break; 382 case EQUAL: 383 case NOT_EQUAL: 384 case GREATERTHAN_OR_EQUALS: 385 break; 386 case LESSTHAN: 387 case APPROXIMATE: 388 case LESSTHAN_OR_EQUALS: 389 case ENDS_BEFORE: 390 throw new IllegalStateException( 391 Msg.code(1928) + "Invalid lower bound comparator: " + theLowerBound.getPrefix()); 392 } 393 } 394 return retVal; 395 } 396 397 public DateParam getUpperBound() { 398 return myUpperBound; 399 } 400 401 /** 402 * Sets the upper bound using a string that is compliant with 403 * FHIR dateTime format (ISO-8601). 404 * <p> 405 * This upper bound is assumed to have a <code>le</code> 406 * (less than or equals) modifier. 407 * </p> 408 * <p> 409 * Note: An operation can take a DateRangeParam. If only a single date is provided, 410 * it will still result in a DateRangeParam where the lower and upper bounds 411 * are the same value. As such, even though the prefixes for the lower and 412 * upper bounds default to <code>ge</code> and <code>le</code> respectively, 413 * the resulting prefix is effectively <code>eq</code> where only a single 414 * date is provided - as required by the FHIR specificiation (i.e. "If no 415 * prefix is present, the prefix <code>eq</code> is assumed"). 416 * </p> 417 */ 418 public DateRangeParam setUpperBound(String theUpperBound) { 419 setUpperBound(new DateParam(LESSTHAN_OR_EQUALS, theUpperBound)); 420 return this; 421 } 422 423 public DateRangeParam setUpperBound(DateParam theUpperBound) { 424 validateAndSet(myLowerBound, theUpperBound); 425 return this; 426 } 427 428 public Date getUpperBoundAsInstant() { 429 if (myUpperBound == null || myUpperBound.getValue() == null) { 430 return null; 431 } 432 433 return getUpperBoundAsInstant(myUpperBound); 434 } 435 436 @Nonnull 437 private static Date getUpperBoundAsInstant(@Nonnull DateParam theUpperBound) { 438 Date retVal = theUpperBound.getValue(); 439 440 if (theUpperBound.getPrecision().ordinal() <= TemporalPrecisionEnum.DAY.ordinal()) { 441 retVal = DateUtils.getHighestInstantFromDate(retVal); 442 } 443 444 if (theUpperBound.getPrefix() != null) { 445 switch (theUpperBound.getPrefix()) { 446 case LESSTHAN: 447 case ENDS_BEFORE: 448 retVal = new Date(retVal.getTime() - 1L); 449 break; 450 case EQUAL: 451 case NOT_EQUAL: 452 case LESSTHAN_OR_EQUALS: 453 retVal = theUpperBound.getPrecision().add(retVal, 1); 454 retVal = new Date(retVal.getTime() - 1L); 455 break; 456 case GREATERTHAN_OR_EQUALS: 457 case GREATERTHAN: 458 case APPROXIMATE: 459 case STARTS_AFTER: 460 throw new IllegalStateException( 461 Msg.code(1929) + "Invalid upper bound comparator: " + theUpperBound.getPrefix()); 462 } 463 } 464 return retVal; 465 } 466 467 @Override 468 public List<DateParam> getValuesAsQueryTokens() { 469 ArrayList<DateParam> retVal = new ArrayList<>(); 470 if (myLowerBound != null && myLowerBound.getMissing() != null) { 471 retVal.add((myLowerBound)); 472 } else { 473 if (myLowerBound != null && !myLowerBound.isEmpty()) { 474 retVal.add((myLowerBound)); 475 } 476 if (myUpperBound != null && !myUpperBound.isEmpty()) { 477 retVal.add((myUpperBound)); 478 } 479 } 480 return retVal; 481 } 482 483 private boolean hasBound(DateParam bound) { 484 return bound != null && !bound.isEmpty(); 485 } 486 487 @Override 488 public int hashCode() { 489 return Objects.hash(myLowerBound, myUpperBound); 490 } 491 492 public boolean isEmpty() { 493 return (getLowerBoundAsInstant() == null) && (getUpperBoundAsInstant() == null); 494 } 495 496 /** 497 * Sets the range from a pair of dates, inclusive on both ends 498 * 499 * @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g. 500 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or 501 * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 502 * @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g. 503 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or 504 * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 505 */ 506 public void setRangeFromDatesInclusive(Date theLowerBound, Date theUpperBound) { 507 DateParam lowerBound = theLowerBound != null ? new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound) : null; 508 DateParam upperBound = theUpperBound != null ? new DateParam(LESSTHAN_OR_EQUALS, theUpperBound) : null; 509 validateAndSet(lowerBound, upperBound); 510 } 511 512 /** 513 * Sets the range from a pair of dates, inclusive on both ends 514 * 515 * @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g. 516 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or 517 * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 518 * @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g. 519 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or 520 * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 521 */ 522 public void setRangeFromDatesInclusive(DateParam theLowerBound, DateParam theUpperBound) { 523 validateAndSet(theLowerBound, theUpperBound); 524 } 525 526 /** 527 * Sets the range from a pair of dates, inclusive on both ends. Note that if 528 * theLowerBound is after theUpperBound, thie method will automatically reverse 529 * the order of the arguments in order to create an inclusive range. 530 * 531 * @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g. 532 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or 533 * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 534 * @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g. 535 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or 536 * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 537 */ 538 public void setRangeFromDatesInclusive(IPrimitiveType<Date> theLowerBound, IPrimitiveType<Date> theUpperBound) { 539 IPrimitiveType<Date> lowerBound = theLowerBound; 540 IPrimitiveType<Date> upperBound = theUpperBound; 541 if (lowerBound != null 542 && lowerBound.getValue() != null 543 && upperBound != null 544 && upperBound.getValue() != null) { 545 if (lowerBound.getValue().after(upperBound.getValue())) { 546 IPrimitiveType<Date> temp = lowerBound; 547 lowerBound = upperBound; 548 upperBound = temp; 549 } 550 } 551 validateAndSet( 552 lowerBound != null ? new DateParam(GREATERTHAN_OR_EQUALS, lowerBound) : null, 553 upperBound != null ? new DateParam(LESSTHAN_OR_EQUALS, upperBound) : null); 554 } 555 556 /** 557 * Sets the range from a pair of dates, inclusive on both ends 558 * 559 * @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g. 560 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or 561 * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 562 * @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g. 563 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or 564 * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 565 */ 566 public void setRangeFromDatesInclusive(String theLowerBound, String theUpperBound) { 567 DateParam lowerBound = theLowerBound != null ? new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound) : null; 568 DateParam upperBound = theUpperBound != null ? new DateParam(LESSTHAN_OR_EQUALS, theUpperBound) : null; 569 if (isNotBlank(theLowerBound) && isNotBlank(theUpperBound) && theLowerBound.equals(theUpperBound)) { 570 lowerBound.setPrefix(EQUAL); 571 upperBound.setPrefix(EQUAL); 572 } 573 validateAndSet(lowerBound, upperBound); 574 } 575 576 @Override 577 public void setValuesAsQueryTokens( 578 FhirContext theContext, String theParamName, List<QualifiedParamList> theParameters) 579 throws InvalidRequestException { 580 581 boolean haveHadUnqualifiedParameter = false; 582 for (QualifiedParamList paramList : theParameters) { 583 if (paramList.size() == 0) { 584 continue; 585 } 586 if (paramList.size() > 1) { 587 throw new InvalidRequestException(Msg.code(1930) + "DateRange parameter does not support OR queries"); 588 } 589 String param = paramList.get(0); 590 591 /* 592 * Since ' ' is escaped as '+' we'll be nice to anyone might have accidentally not 593 * escaped theirs 594 */ 595 param = param.replace(' ', '+'); 596 597 DateParam parsed = new DateParam(); 598 parsed.setValueAsQueryToken(theContext, theParamName, paramList.getQualifier(), param); 599 addParam(parsed); 600 601 if (parsed.getPrefix() == null) { 602 if (haveHadUnqualifiedParameter) { 603 throw new InvalidRequestException( 604 Msg.code(1931) 605 + "Multiple date parameters with the same name and no qualifier (>, <, etc.) is not supported"); 606 } 607 haveHadUnqualifiedParameter = true; 608 } 609 } 610 } 611 612 @Override 613 public String toString() { 614 StringBuilder b = new StringBuilder(); 615 b.append(getClass().getSimpleName()); 616 b.append("["); 617 if (hasBound(myLowerBound)) { 618 if (myLowerBound.getPrefix() != null) { 619 b.append(myLowerBound.getPrefix().getValue()); 620 } 621 b.append(myLowerBound.getValueAsString()); 622 } 623 if (hasBound(myUpperBound)) { 624 if (hasBound(myLowerBound)) { 625 b.append(" "); 626 } 627 if (myUpperBound.getPrefix() != null) { 628 b.append(myUpperBound.getPrefix().getValue()); 629 } 630 b.append(myUpperBound.getValueAsString()); 631 } else { 632 if (!hasBound(myLowerBound)) { 633 b.append("empty"); 634 } 635 } 636 b.append("]"); 637 return b.toString(); 638 } 639 640 /** 641 * Note: An operation can take a DateRangeParam. If only a single date is provided, 642 * it will still result in a DateRangeParam where the lower and upper bounds 643 * are the same value. As such, even though the prefixes for the lower and 644 * upper bounds default to <code>ge</code> and <code>le</code> respectively, 645 * the resulting prefix is effectively <code>eq</code> where only a single 646 * date is provided - as required by the FHIR specification (i.e. "If no 647 * prefix is present, the prefix <code>eq</code> is assumed"). 648 */ 649 private void validateAndSet(DateParam lowerBound, DateParam upperBound) { 650 if (hasBound(lowerBound) && hasBound(upperBound)) { 651 Date lowerBoundAsInstant = getLowerBoundAsInstant(lowerBound); 652 Date upperBoundAsInstant = getUpperBoundAsInstant(upperBound); 653 if (lowerBoundAsInstant.after(upperBoundAsInstant)) { 654 throw new DataFormatException(Msg.code(1932) 655 + format( 656 "Lower bound of %s is after upper bound of %s", 657 lowerBound.getValueAsString(), upperBound.getValueAsString())); 658 } 659 } 660 661 if (hasBound(lowerBound)) { 662 if (lowerBound.getPrefix() == null) { 663 lowerBound.setPrefix(GREATERTHAN_OR_EQUALS); 664 } 665 switch (lowerBound.getPrefix()) { 666 case GREATERTHAN: 667 case GREATERTHAN_OR_EQUALS: 668 default: 669 break; 670 case LESSTHAN: 671 case LESSTHAN_OR_EQUALS: 672 throw new DataFormatException(Msg.code(1933) + "Lower bound comparator must be > or >=, can not be " 673 + lowerBound.getPrefix().getValue()); 674 } 675 } 676 677 if (hasBound(upperBound)) { 678 if (upperBound.getPrefix() == null) { 679 upperBound.setPrefix(LESSTHAN_OR_EQUALS); 680 } 681 switch (upperBound.getPrefix()) { 682 case LESSTHAN: 683 case LESSTHAN_OR_EQUALS: 684 default: 685 break; 686 case GREATERTHAN: 687 case GREATERTHAN_OR_EQUALS: 688 throw new DataFormatException(Msg.code(1934) + "Upper bound comparator must be < or <=, can not be " 689 + upperBound.getPrefix().getValue()); 690 } 691 } 692 693 myLowerBound = lowerBound; 694 myUpperBound = upperBound; 695 } 696}