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