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