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