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