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