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