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