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