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