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