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