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}