001package ca.uhn.fhir.rest.param; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2016 University Health Network 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import java.util.ArrayList; 024import java.util.Date; 025import java.util.List; 026 027import org.hl7.fhir.instance.model.api.IPrimitiveType; 028 029import ca.uhn.fhir.model.api.IQueryParameterAnd; 030import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; 031import ca.uhn.fhir.model.primitive.DateTimeDt; 032import ca.uhn.fhir.parser.DataFormatException; 033import ca.uhn.fhir.rest.method.QualifiedParamList; 034import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 035 036public class DateRangeParam implements IQueryParameterAnd<DateParam> { 037 038 private DateParam myLowerBound; 039 private DateParam myUpperBound; 040 041 /** 042 * Basic constructor. Values must be supplied by calling {@link #setLowerBound(DateParam)} and 043 * {@link #setUpperBound(DateParam)} 044 */ 045 public DateRangeParam() { 046 // nothing 047 } 048 049 /** 050 * Constructor which takes two Dates representing the lower and upper bounds of the range (inclusive on both ends) 051 * 052 * @param theLowerBound 053 * A qualified date param representing the lower date bound (optionally may include time), e.g. 054 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 055 * @param theUpperBound 056 * A qualified date param representing the upper date bound (optionally may include time), e.g. 057 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 058 */ 059 public DateRangeParam(Date theLowerBound, Date theUpperBound) { 060 setRangeFromDatesInclusive(theLowerBound, theUpperBound); 061 } 062 063 /** 064 * Sets the range from a single date param. If theDateParam has no qualifier, treats it as the lower and upper bound 065 * (e.g. 2011-01-02 would match any time on that day). If theDateParam has a qualifier, treats it as either the 066 * lower or upper bound, with no opposite bound. 067 */ 068 public DateRangeParam(DateParam theDateParam) { 069 if (theDateParam == null) { 070 throw new NullPointerException("theDateParam can not be null"); 071 } 072 if (theDateParam.isEmpty()) { 073 throw new IllegalArgumentException("theDateParam can not be empty"); 074 } 075 if (theDateParam.getComparator() == null) { 076 setRangeFromDatesInclusive(theDateParam.getValueAsString(), theDateParam.getValueAsString()); 077 } else { 078 switch (theDateParam.getComparator()) { 079 case GREATERTHAN: 080 case GREATERTHAN_OR_EQUALS: 081 myLowerBound = theDateParam; 082 myUpperBound = null; 083 break; 084 case LESSTHAN: 085 case LESSTHAN_OR_EQUALS: 086 myLowerBound = null; 087 myUpperBound = theDateParam; 088 break; 089 default: 090 // Should not happen 091 throw new IllegalStateException("Unknown comparator:" + theDateParam.getComparator() + ". This is a bug."); 092 } 093 } 094 validateAndThrowDataFormatExceptionIfInvalid(); 095 } 096 097 /** 098 * Constructor which takes two Dates representing the lower and upper bounds of the range (inclusive on both ends) 099 * 100 * @param theLowerBound 101 * A qualified date param representing the lower date bound (optionally may include time), e.g. 102 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 103 * @param theUpperBound 104 * A qualified date param representing the upper date bound (optionally may include time), e.g. 105 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 106 */ 107 public DateRangeParam(DateTimeDt theLowerBound, DateTimeDt theUpperBound) { 108 setRangeFromDatesInclusive(theLowerBound, theUpperBound); 109 } 110 111 /** 112 * Constructor which takes two Dates representing the lower and upper bounds of the range (inclusive on both ends) 113 * 114 * @param theLowerBound 115 * A qualified date param representing the lower date bound (optionally may include time), e.g. 116 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 117 * @param theUpperBound 118 * A qualified date param representing the upper date bound (optionally may include time), e.g. 119 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 120 */ 121 public DateRangeParam(IPrimitiveType<Date> theLowerBound, IPrimitiveType<Date> theUpperBound) { 122 setRangeFromDatesInclusive(theLowerBound, theUpperBound); 123 } 124 125 /** 126 * Constructor which takes two strings representing the lower and upper bounds of the range (inclusive on both ends) 127 * 128 * @param theLowerBound 129 * An unqualified date param representing the lower date bound (optionally may include time), e.g. 130 * "2011-02-22" or "2011-02-22T13:12:00Z". Either theLowerBound or theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 131 * @param theUpperBound 132 * An unqualified date param representing the upper date bound (optionally may include time), e.g. 133 * "2011-02-22" or "2011-02-22T13:12:00Z". Either theLowerBound or theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 134 */ 135 public DateRangeParam(String theLowerBound, String theUpperBound) { 136 setRangeFromDatesInclusive(theLowerBound, theUpperBound); 137 } 138 139 private void addParam(DateParam theParsed) throws InvalidRequestException { 140 if (theParsed.getComparator() == null) { 141 if (myLowerBound != null || myUpperBound != null) { 142 throw new InvalidRequestException("Can not have multiple date range parameters for the same param without a qualifier"); 143 } 144 145 myLowerBound = new DateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, theParsed.getValueAsString()); 146 myUpperBound = new DateParam(QuantityCompararatorEnum.LESSTHAN_OR_EQUALS, theParsed.getValueAsString()); 147 148 } else { 149 150 switch (theParsed.getComparator()) { 151 case GREATERTHAN: 152 case GREATERTHAN_OR_EQUALS: 153 if (myLowerBound != null) { 154 throw new InvalidRequestException("Can not have multiple date range parameters for the same param that specify a lower bound"); 155 } 156 myLowerBound = theParsed; 157 break; 158 case LESSTHAN: 159 case LESSTHAN_OR_EQUALS: 160 if (myUpperBound != null) { 161 throw new InvalidRequestException("Can not have multiple date range parameters for the same param that specify an upper bound"); 162 } 163 myUpperBound = theParsed; 164 break; 165 default: 166 throw new InvalidRequestException("Unknown comparator: " + theParsed.getComparator()); 167 } 168 169 } 170 } 171 172 public DateParam getLowerBound() { 173 return myLowerBound; 174 } 175 176 public Date getLowerBoundAsInstant() { 177 if (myLowerBound == null) { 178 return null; 179 } 180 Date retVal = myLowerBound.getValue(); 181 if (myLowerBound.getComparator() != null) { 182 switch (myLowerBound.getComparator()) { 183 case GREATERTHAN: 184 retVal = myLowerBound.getPrecision().add(retVal, 1); 185 break; 186 case GREATERTHAN_OR_EQUALS: 187 break; 188 case LESSTHAN: 189 case LESSTHAN_OR_EQUALS: 190 throw new IllegalStateException("Unvalid lower bound comparator: " + myLowerBound.getComparator()); 191 } 192 } 193 return retVal; 194 } 195 196 public DateParam getUpperBound() { 197 return myUpperBound; 198 } 199 200 public Date getUpperBoundAsInstant() { 201 if (myUpperBound == null) { 202 return null; 203 } 204 Date retVal = myUpperBound.getValue(); 205 if (myUpperBound.getComparator() != null) { 206 switch (myUpperBound.getComparator()) { 207 case LESSTHAN: 208 retVal = new Date(retVal.getTime() - 1L); 209 break; 210 case LESSTHAN_OR_EQUALS: 211 retVal = myUpperBound.getPrecision().add(retVal, 1); 212 retVal = new Date(retVal.getTime() - 1L); 213 break; 214 case GREATERTHAN_OR_EQUALS: 215 case GREATERTHAN: 216 throw new IllegalStateException("Unvalid upper bound comparator: " + myUpperBound.getComparator()); 217 } 218 } 219 return retVal; 220 } 221 222 @Override 223 public List<DateParam> getValuesAsQueryTokens() { 224 ArrayList<DateParam> retVal = new ArrayList<DateParam>(); 225 if (myLowerBound != null && !myLowerBound.isEmpty()) { 226 retVal.add((myLowerBound)); 227 } 228 if (myUpperBound != null && !myUpperBound.isEmpty()) { 229 retVal.add((myUpperBound)); 230 } 231 return retVal; 232 } 233 234 private boolean haveLowerBound() { 235 return myLowerBound != null && myLowerBound.isEmpty() == false; 236 } 237 238 private boolean haveUpperBound() { 239 return myUpperBound != null && myUpperBound.isEmpty() == false; 240 } 241 242 public boolean isEmpty() { 243 return (getLowerBoundAsInstant() == null) && (getUpperBoundAsInstant() == null); 244 } 245 246 public void setLowerBound(DateParam theLowerBound) { 247 myLowerBound = theLowerBound; 248 validateAndThrowDataFormatExceptionIfInvalid(); 249 } 250 251 /** 252 * Sets the range from a pair of dates, inclusive on both ends 253 * 254 * @param theLowerBound 255 * A qualified date param representing the lower date bound (optionally may include time), e.g. 256 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 257 * @param theUpperBound 258 * A qualified date param representing the upper date bound (optionally may include time), e.g. 259 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 260 */ 261 public void setRangeFromDatesInclusive(Date theLowerBound, Date theUpperBound) { 262 myLowerBound = theLowerBound != null ? new DateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, theLowerBound) : null; 263 myUpperBound = theUpperBound != null ? new DateParam(QuantityCompararatorEnum.LESSTHAN_OR_EQUALS, theUpperBound) : null; 264 validateAndThrowDataFormatExceptionIfInvalid(); 265 } 266 267 /** 268 * Sets the range from a pair of dates, inclusive on both ends 269 * 270 * @param theLowerBound 271 * A qualified date param representing the lower date bound (optionally may include time), e.g. 272 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 273 * @param theUpperBound 274 * A qualified date param representing the upper date bound (optionally may include time), e.g. 275 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 276 */ 277 public void setRangeFromDatesInclusive(DateTimeDt theLowerBound, DateTimeDt theUpperBound) { 278 if (theLowerBound instanceof DateParam) { 279 myLowerBound = (DateParam) theLowerBound; 280 } else { 281 myLowerBound = theLowerBound != null ? new DateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, theLowerBound) : null; 282 } 283 if (theUpperBound instanceof DateParam) { 284 myUpperBound = (DateParam) theUpperBound; 285 } else { 286 myUpperBound = theUpperBound != null ? new DateParam(QuantityCompararatorEnum.LESSTHAN_OR_EQUALS, theUpperBound) : null; 287 } 288 validateAndThrowDataFormatExceptionIfInvalid(); 289 } 290 291 /** 292 * Sets the range from a pair of dates, inclusive on both ends 293 * 294 * @param theLowerBound 295 * A qualified date param representing the lower date bound (optionally may include time), e.g. 296 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 297 * @param theUpperBound 298 * A qualified date param representing the upper date bound (optionally may include time), e.g. 299 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 300 */ 301 public void setRangeFromDatesInclusive(IPrimitiveType<Date> theLowerBound, IPrimitiveType<Date> theUpperBound) { 302 myLowerBound = theLowerBound != null ? new DateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, theLowerBound) : null; 303 myUpperBound = theUpperBound != null ? new DateParam(QuantityCompararatorEnum.LESSTHAN_OR_EQUALS, theUpperBound) : null; 304 validateAndThrowDataFormatExceptionIfInvalid(); 305 } 306 /** 307 * Sets the range from a pair of dates, inclusive on both ends 308 * 309 * @param theLowerBound 310 * A qualified date param representing the lower date bound (optionally may include time), e.g. 311 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 312 * @param theUpperBound 313 * A qualified date param representing the upper date bound (optionally may include time), e.g. 314 * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. 315 */ 316 public void setRangeFromDatesInclusive(String theLowerBound, String theUpperBound) { 317 myLowerBound = theLowerBound != null ? new DateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, theLowerBound) : null; 318 myUpperBound = theUpperBound != null ? new DateParam(QuantityCompararatorEnum.LESSTHAN_OR_EQUALS, theUpperBound) : null; 319 validateAndThrowDataFormatExceptionIfInvalid(); 320 } 321 322 public void setUpperBound(DateParam theUpperBound) { 323 myUpperBound = theUpperBound; 324 validateAndThrowDataFormatExceptionIfInvalid(); 325 } 326 327 @Override 328 public void setValuesAsQueryTokens(List<QualifiedParamList> theParameters) throws InvalidRequestException { 329 330 boolean haveHadUnqualifiedParameter = false; 331 for (QualifiedParamList paramList : theParameters) { 332 if (paramList.size() == 0) { 333 continue; 334 } 335 if (paramList.size() > 1) { 336 throw new InvalidRequestException("DateRange parameter does not suppport OR queries"); 337 } 338 String param = paramList.get(0); 339 DateParam parsed = new DateParam(); 340 parsed.setValueAsQueryToken(paramList.getQualifier(), param); 341 addParam(parsed); 342 343 if (parsed.getComparator() == null) { 344 if (haveHadUnqualifiedParameter) { 345 throw new InvalidRequestException("Multiple date parameters with the same name and no qualifier (>, <, etc.) is not supported"); 346 } 347 haveHadUnqualifiedParameter=true; 348 } 349 350 } 351 352 } 353 354 @Override 355 public String toString() { 356 StringBuilder b = new StringBuilder(); 357 b.append(getClass().getSimpleName()); 358 b.append("["); 359 if (haveLowerBound()) { 360 if (myLowerBound.getComparator() != null) { 361 b.append(myLowerBound.getComparator().getCode()); 362 } 363 b.append(myLowerBound.getValueAsString()); 364 } 365 if (haveUpperBound()) { 366 if(haveLowerBound()) { 367 b.append(" "); 368 } 369 if (myUpperBound.getComparator() != null) { 370 b.append(myUpperBound.getComparator().getCode()); 371 } 372 b.append(myUpperBound.getValueAsString()); 373 } else { 374 if (!haveLowerBound()) { 375 b.append("empty"); 376 } 377 } 378 b.append("]"); 379 return b.toString(); 380 } 381 382 private void validateAndThrowDataFormatExceptionIfInvalid() { 383 boolean haveLowerBound = haveLowerBound(); 384 boolean haveUpperBound = haveUpperBound(); 385 if (haveLowerBound && haveUpperBound) { 386 if (myLowerBound.getValue().getTime() > myUpperBound.getValue().getTime()) { 387 StringBuilder b = new StringBuilder(); 388 b.append("Lower bound of "); 389 b.append(myLowerBound.getValueAsString()); 390 b.append(" is after upper bound of "); 391 b.append(myUpperBound.getValueAsString()); 392 throw new DataFormatException(b.toString()); 393 } 394 } 395 396 if (haveLowerBound) { 397 if (myLowerBound.getComparator() == null) { 398 myLowerBound.setComparator(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS); 399 } 400 switch (myLowerBound.getComparator()) { 401 case GREATERTHAN: 402 case GREATERTHAN_OR_EQUALS: 403 default: 404 break; 405 case LESSTHAN: 406 case LESSTHAN_OR_EQUALS: 407 throw new DataFormatException("Lower bound comparator must be > or >=, can not be " + myLowerBound.getComparator().getCode()); 408 } 409 } 410 411 if (haveUpperBound) { 412 if (myUpperBound.getComparator() == null) { 413 myUpperBound.setComparator(QuantityCompararatorEnum.LESSTHAN_OR_EQUALS); 414 } 415 switch (myUpperBound.getComparator()) { 416 case LESSTHAN: 417 case LESSTHAN_OR_EQUALS: 418 default: 419 break; 420 case GREATERTHAN: 421 case GREATERTHAN_OR_EQUALS: 422 throw new DataFormatException("Upper bound comparator must be < or <=, can not be " + myUpperBound.getComparator().getCode()); 423 } 424 } 425 426 } 427 428}