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