diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IHistoryTyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IHistoryTyped.java index 81051740745..a8234f0e8c2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IHistoryTyped.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IHistoryTyped.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.gclient; * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,16 +20,23 @@ package ca.uhn.fhir.rest.gclient; * #L% */ -import java.util.Date; - +import ca.uhn.fhir.rest.param.DateRangeParam; import org.hl7.fhir.instance.model.api.IPrimitiveType; -public interface IHistoryTyped extends IClientExecutable, T> { +import java.util.Date; + +public interface IHistoryTyped extends IClientExecutable, T> { /** - * Request that the server return only resource versions that were created at or after the given time (inclusive) + * Request that the server return only the history elements between the + * specific range */ - IHistoryTyped since(Date theCutoff); + IHistoryTyped at(DateRangeParam theDateRangeParam); + + /** + * Request that the server return only up to theCount number of resources + */ + IHistoryTyped count(Integer theCount); /** * Request that the server return only resource versions that were created at or after the given time (inclusive) @@ -41,9 +48,9 @@ public interface IHistoryTyped extends IClientExecutable, T> IHistoryTyped since(IPrimitiveType theCutoff); /** - * Request that the server return only up to theCount number of resources + * Request that the server return only resource versions that were created at or after the given time (inclusive) */ - IHistoryTyped count(Integer theCount); - - + IHistoryTyped since(Date theCutoff); + + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java index c73a5ea3835..e9b60efdbb8 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java @@ -1,8 +1,18 @@ package ca.uhn.fhir.rest.param; -import static ca.uhn.fhir.rest.param.ParamPrefixEnum.EQUAL; -import static ca.uhn.fhir.rest.param.ParamPrefixEnum.GREATERTHAN_OR_EQUALS; -import static ca.uhn.fhir.rest.param.ParamPrefixEnum.LESSTHAN_OR_EQUALS; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.IQueryParameterAnd; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.rest.api.QualifiedParamList; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import org.hl7.fhir.instance.model.api.IPrimitiveType; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Objects; + +import static ca.uhn.fhir.rest.param.ParamPrefixEnum.*; import static java.lang.String.format; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -15,9 +25,9 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -25,20 +35,11 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * limitations under the License. * #L% */ -import java.util.*; - -import org.hl7.fhir.instance.model.api.IPrimitiveType; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.IQueryParameterAnd; -import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.rest.api.QualifiedParamList; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; public class DateRangeParam implements IQueryParameterAnd { private static final long serialVersionUID = 1L; - + private DateParam myLowerBound; private DateParam myUpperBound; @@ -52,15 +53,13 @@ public class DateRangeParam implements IQueryParameterAnd { /** * Constructor which takes two Dates representing the lower and upper bounds of the range (inclusive on both ends) - * - * @param theLowerBound - * A qualified date param representing the lower date bound (optionally may include time), e.g. - * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or - * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. - * @param theUpperBound - * A qualified date param representing the upper date bound (optionally may include time), e.g. - * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or - * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. + * + * @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g. + * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or + * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. + * @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g. + * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or + * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. */ public DateRangeParam(Date theLowerBound, Date theUpperBound) { this(); @@ -84,37 +83,35 @@ public class DateRangeParam implements IQueryParameterAnd { setRangeFromDatesInclusive(theDateParam.getValueAsString(), theDateParam.getValueAsString()); } else { switch (theDateParam.getPrefix()) { - case EQUAL: - setRangeFromDatesInclusive(theDateParam.getValueAsString(), theDateParam.getValueAsString()); - break; - case STARTS_AFTER: - case GREATERTHAN: - case GREATERTHAN_OR_EQUALS: - validateAndSet(theDateParam, null); - break; - case ENDS_BEFORE: - case LESSTHAN: - case LESSTHAN_OR_EQUALS: - validateAndSet(null, theDateParam); - break; - default: - // Should not happen - throw new InvalidRequestException("Invalid comparator for date range parameter:" + theDateParam.getPrefix() + ". This is a bug."); + case EQUAL: + setRangeFromDatesInclusive(theDateParam.getValueAsString(), theDateParam.getValueAsString()); + break; + case STARTS_AFTER: + case GREATERTHAN: + case GREATERTHAN_OR_EQUALS: + validateAndSet(theDateParam, null); + break; + case ENDS_BEFORE: + case LESSTHAN: + case LESSTHAN_OR_EQUALS: + validateAndSet(null, theDateParam); + break; + default: + // Should not happen + throw new InvalidRequestException("Invalid comparator for date range parameter:" + theDateParam.getPrefix() + ". This is a bug."); } } } /** * Constructor which takes two Dates representing the lower and upper bounds of the range (inclusive on both ends) - * - * @param theLowerBound - * A qualified date param representing the lower date bound (optionally may include time), e.g. - * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or - * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. - * @param theUpperBound - * A qualified date param representing the upper date bound (optionally may include time), e.g. - * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or - * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. + * + * @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g. + * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or + * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. + * @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g. + * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or + * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. */ public DateRangeParam(DateParam theLowerBound, DateParam theUpperBound) { this(); @@ -123,15 +120,13 @@ public class DateRangeParam implements IQueryParameterAnd { /** * Constructor which takes two Dates representing the lower and upper bounds of the range (inclusive on both ends) - * - * @param theLowerBound - * A qualified date param representing the lower date bound (optionally may include time), e.g. - * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or - * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. - * @param theUpperBound - * A qualified date param representing the upper date bound (optionally may include time), e.g. - * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or - * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. + * + * @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g. + * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or + * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. + * @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g. + * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or + * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. */ public DateRangeParam(IPrimitiveType theLowerBound, IPrimitiveType theUpperBound) { this(); @@ -140,15 +135,13 @@ public class DateRangeParam implements IQueryParameterAnd { /** * Constructor which takes two strings representing the lower and upper bounds of the range (inclusive on both ends) - * - * @param theLowerBound - * An unqualified date param representing the lower date bound (optionally may include time), e.g. - * "2011-02-22" or "2011-02-22T13:12:00Z". Either theLowerBound or theUpperBound may both be populated, or - * one may be null, but it is not valid for both to be null. - * @param theUpperBound - * An unqualified date param representing the upper date bound (optionally may include time), e.g. - * "2011-02-22" or "2011-02-22T13:12:00Z". Either theLowerBound or theUpperBound may both be populated, or - * one may be null, but it is not valid for both to be null. + * + * @param theLowerBound An unqualified date param representing the lower date bound (optionally may include time), e.g. + * "2011-02-22" or "2011-02-22T13:12:00Z". Either theLowerBound or theUpperBound may both be populated, or + * one may be null, but it is not valid for both to be null. + * @param theUpperBound An unqualified date param representing the upper date bound (optionally may include time), e.g. + * "2011-02-22" or "2011-02-22T13:12:00Z". Either theLowerBound or theUpperBound may both be populated, or + * one may be null, but it is not valid for both to be null. */ public DateRangeParam(String theLowerBound, String theUpperBound) { this(); @@ -168,35 +161,53 @@ public class DateRangeParam implements IQueryParameterAnd { myLowerBound = new DateParam(EQUAL, theParsed.getValueAsString()); myUpperBound = new DateParam(EQUAL, theParsed.getValueAsString()); } - + } else { switch (theParsed.getPrefix()) { - case GREATERTHAN: - case GREATERTHAN_OR_EQUALS: - if (myLowerBound != null) { - throw new InvalidRequestException("Can not have multiple date range parameters for the same param that specify a lower bound"); - } - myLowerBound = theParsed; - break; - case LESSTHAN: - case LESSTHAN_OR_EQUALS: - if (myUpperBound != null) { - throw new InvalidRequestException("Can not have multiple date range parameters for the same param that specify an upper bound"); - } - myUpperBound = theParsed; - break; - default: - throw new InvalidRequestException("Unknown comparator: " + theParsed.getPrefix()); + case GREATERTHAN: + case GREATERTHAN_OR_EQUALS: + if (myLowerBound != null) { + throw new InvalidRequestException("Can not have multiple date range parameters for the same param that specify a lower bound"); + } + myLowerBound = theParsed; + break; + case LESSTHAN: + case LESSTHAN_OR_EQUALS: + if (myUpperBound != null) { + throw new InvalidRequestException("Can not have multiple date range parameters for the same param that specify an upper bound"); + } + myUpperBound = theParsed; + break; + default: + throw new InvalidRequestException("Unknown comparator: " + theParsed.getPrefix()); } } } + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof DateRangeParam)) { + return false; + } + DateRangeParam other = (DateRangeParam) obj; + return Objects.equals(myLowerBound, other.myLowerBound) && + Objects.equals(myUpperBound, other.myUpperBound); + } + public DateParam getLowerBound() { return myLowerBound; } + public DateRangeParam setLowerBound(DateParam theLowerBound) { + validateAndSet(theLowerBound, myUpperBound); + return this; + } + public Date getLowerBoundAsInstant() { if (myLowerBound == null) { return null; @@ -204,19 +215,19 @@ public class DateRangeParam implements IQueryParameterAnd { Date retVal = myLowerBound.getValue(); if (myLowerBound.getPrefix() != null) { switch (myLowerBound.getPrefix()) { - case GREATERTHAN: - case STARTS_AFTER: - retVal = myLowerBound.getPrecision().add(retVal, 1); - break; - case EQUAL: - case GREATERTHAN_OR_EQUALS: - break; - case LESSTHAN: - case APPROXIMATE: - case LESSTHAN_OR_EQUALS: - case ENDS_BEFORE: - case NOT_EQUAL: - throw new IllegalStateException("Unvalid lower bound comparator: " + myLowerBound.getPrefix()); + case GREATERTHAN: + case STARTS_AFTER: + retVal = myLowerBound.getPrecision().add(retVal, 1); + break; + case EQUAL: + case GREATERTHAN_OR_EQUALS: + break; + case LESSTHAN: + case APPROXIMATE: + case LESSTHAN_OR_EQUALS: + case ENDS_BEFORE: + case NOT_EQUAL: + throw new IllegalStateException("Unvalid lower bound comparator: " + myLowerBound.getPrefix()); } } return retVal; @@ -226,6 +237,11 @@ public class DateRangeParam implements IQueryParameterAnd { return myUpperBound; } + public DateRangeParam setUpperBound(DateParam theUpperBound) { + validateAndSet(myLowerBound, theUpperBound); + return this; + } + public Date getUpperBoundAsInstant() { if (myUpperBound == null) { return null; @@ -233,21 +249,21 @@ public class DateRangeParam implements IQueryParameterAnd { Date retVal = myUpperBound.getValue(); if (myUpperBound.getPrefix() != null) { switch (myUpperBound.getPrefix()) { - case LESSTHAN: - case ENDS_BEFORE: - retVal = new Date(retVal.getTime() - 1L); - break; - case EQUAL: - case LESSTHAN_OR_EQUALS: - retVal = myUpperBound.getPrecision().add(retVal, 1); - retVal = new Date(retVal.getTime() - 1L); - break; - case GREATERTHAN_OR_EQUALS: - case GREATERTHAN: - case APPROXIMATE: - case NOT_EQUAL: - case STARTS_AFTER: - throw new IllegalStateException("Unvalid upper bound comparator: " + myUpperBound.getPrefix()); + case LESSTHAN: + case ENDS_BEFORE: + retVal = new Date(retVal.getTime() - 1L); + break; + case EQUAL: + case LESSTHAN_OR_EQUALS: + retVal = myUpperBound.getPrecision().add(retVal, 1); + retVal = new Date(retVal.getTime() - 1L); + break; + case GREATERTHAN_OR_EQUALS: + case GREATERTHAN: + case APPROXIMATE: + case NOT_EQUAL: + case STARTS_AFTER: + throw new IllegalStateException("Unvalid upper bound comparator: " + myUpperBound.getPrefix()); } } return retVal; @@ -273,46 +289,55 @@ public class DateRangeParam implements IQueryParameterAnd { return bound != null && !bound.isEmpty(); } + @Override + public int hashCode() { + return Objects.hash(myLowerBound, myUpperBound); + } + public boolean isEmpty() { return (getLowerBoundAsInstant() == null) && (getUpperBoundAsInstant() == null); } - public DateRangeParam setLowerBound(DateParam theLowerBound) { - validateAndSet(theLowerBound, myUpperBound); + /** + * Sets the lower bound using a string that is compliant with + * FHIR dateTime format (ISO-8601). + *

+ * This lower bound is assumed to have a ge + * (greater than or equals) modifier. + *

+ */ + public DateRangeParam setLowerBound(String theLowerBound) { + setLowerBound(new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound)); return this; } /** * Sets the range from a pair of dates, inclusive on both ends - * - * @param theLowerBound - * A qualified date param representing the lower date bound (optionally may include time), e.g. - * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or - * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. - * @param theUpperBound - * A qualified date param representing the upper date bound (optionally may include time), e.g. - * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or - * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. + * + * @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g. + * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or + * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. + * @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g. + * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or + * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. */ public void setRangeFromDatesInclusive(Date theLowerBound, Date theUpperBound) { DateParam lowerBound = theLowerBound != null - ? new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound) : null; + ? new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound) : null; DateParam upperBound = theUpperBound != null - ? new DateParam(LESSTHAN_OR_EQUALS, theUpperBound) : null; + ? new DateParam(LESSTHAN_OR_EQUALS, theUpperBound) : null; validateAndSet(lowerBound, upperBound); } /** * Sets the range from a pair of dates, inclusive on both ends - * - * @param theLowerBound - * A qualified date param representing the lower date bound (optionally may include time), e.g. - * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or - * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. - * @param theUpperBound - * A qualified date param representing the upper date bound (optionally may include time), e.g. - * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or - * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. + * + * @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g. + * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or + * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. + * @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g. + * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or + * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. */ public void setRangeFromDatesInclusive(DateParam theLowerBound, DateParam theUpperBound) { validateAndSet(theLowerBound, theUpperBound); @@ -322,15 +347,13 @@ public class DateRangeParam implements IQueryParameterAnd { * Sets the range from a pair of dates, inclusive on both ends. Note that if * theLowerBound is after theUpperBound, thie method will automatically reverse * the order of the arguments in order to create an inclusive range. - * - * @param theLowerBound - * A qualified date param representing the lower date bound (optionally may include time), e.g. - * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or - * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. - * @param theUpperBound - * A qualified date param representing the upper date bound (optionally may include time), e.g. - * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or - * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. + * + * @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g. + * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or + * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. + * @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g. + * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or + * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. */ public void setRangeFromDatesInclusive(IPrimitiveType theLowerBound, IPrimitiveType theUpperBound) { IPrimitiveType lowerBound = theLowerBound; @@ -349,23 +372,21 @@ public class DateRangeParam implements IQueryParameterAnd { /** * Sets the range from a pair of dates, inclusive on both ends - * - * @param theLowerBound - * A qualified date param representing the lower date bound (optionally may include time), e.g. - * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or - * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. - * @param theUpperBound - * A qualified date param representing the upper date bound (optionally may include time), e.g. - * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or - * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. + * + * @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g. + * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or + * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. + * @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g. + * "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or + * theUpperBound may both be populated, or one may be null, but it is not valid for both to be null. */ public void setRangeFromDatesInclusive(String theLowerBound, String theUpperBound) { DateParam lowerBound = theLowerBound != null - ? new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound) - : null; + ? new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound) + : null; DateParam upperBound = theUpperBound != null - ? new DateParam(LESSTHAN_OR_EQUALS, theUpperBound) - : null; + ? new DateParam(LESSTHAN_OR_EQUALS, theUpperBound) + : null; if (isNotBlank(theLowerBound) && isNotBlank(theUpperBound) && theLowerBound.equals(theUpperBound)) { lowerBound.setPrefix(EQUAL); upperBound.setPrefix(EQUAL); @@ -373,14 +394,22 @@ public class DateRangeParam implements IQueryParameterAnd { validateAndSet(lowerBound, upperBound); } - public DateRangeParam setUpperBound(DateParam theUpperBound) { - validateAndSet(myLowerBound, theUpperBound); + /** + * Sets the upper bound using a string that is compliant with + * FHIR dateTime format (ISO-8601). + *

+ * This upper bound is assumed to have a le + * (less than or equals) modifier. + *

+ */ + public DateRangeParam setUpperBound(String theUpperBound) { + setUpperBound(new DateParam(LESSTHAN_OR_EQUALS, theUpperBound)); return this; } @Override public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, List theParameters) - throws InvalidRequestException { + throws InvalidRequestException { boolean haveHadUnqualifiedParameter = false; for (QualifiedParamList paramList : theParameters) { @@ -391,13 +420,13 @@ public class DateRangeParam implements IQueryParameterAnd { throw new InvalidRequestException("DateRange parameter does not suppport OR queries"); } String param = paramList.get(0); - + /* * Since ' ' is escaped as '+' we'll be nice to anyone might have accidentally not * escaped theirs */ param = param.replace(' ', '+'); - + DateParam parsed = new DateParam(); parsed.setValueAsQueryToken(theContext, theParamName, paramList.getQualifier(), param); addParam(parsed); @@ -413,24 +442,6 @@ public class DateRangeParam implements IQueryParameterAnd { } - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof DateRangeParam)) { - return false; - } - DateRangeParam other = (DateRangeParam) obj; - return Objects.equals(myLowerBound, other.myLowerBound) && - Objects.equals(myUpperBound, other.myUpperBound); - } - - @Override - public int hashCode() { - return Objects.hash(myLowerBound, myUpperBound); - } - @Override public String toString() { StringBuilder b = new StringBuilder(); @@ -463,8 +474,8 @@ public class DateRangeParam implements IQueryParameterAnd { if (hasBound(lowerBound) && hasBound(upperBound)) { if (lowerBound.getValue().getTime() > upperBound.getValue().getTime()) { throw new DataFormatException(format( - "Lower bound of %s is after upper bound of %s", - lowerBound.getValueAsString(), upperBound.getValueAsString())); + "Lower bound of %s is after upper bound of %s", + lowerBound.getValueAsString(), upperBound.getValueAsString())); } } @@ -473,13 +484,13 @@ public class DateRangeParam implements IQueryParameterAnd { lowerBound.setPrefix(GREATERTHAN_OR_EQUALS); } switch (lowerBound.getPrefix()) { - case GREATERTHAN: - case GREATERTHAN_OR_EQUALS: - default: - break; - case LESSTHAN: - case LESSTHAN_OR_EQUALS: - throw new DataFormatException("Lower bound comparator must be > or >=, can not be " + lowerBound.getPrefix().getValue()); + case GREATERTHAN: + case GREATERTHAN_OR_EQUALS: + default: + break; + case LESSTHAN: + case LESSTHAN_OR_EQUALS: + throw new DataFormatException("Lower bound comparator must be > or >=, can not be " + lowerBound.getPrefix().getValue()); } } @@ -488,13 +499,13 @@ public class DateRangeParam implements IQueryParameterAnd { upperBound.setPrefix(LESSTHAN_OR_EQUALS); } switch (upperBound.getPrefix()) { - case LESSTHAN: - case LESSTHAN_OR_EQUALS: - default: - break; - case GREATERTHAN: - case GREATERTHAN_OR_EQUALS: - throw new DataFormatException("Upper bound comparator must be < or <=, can not be " + upperBound.getPrefix().getValue()); + case LESSTHAN: + case LESSTHAN_OR_EQUALS: + default: + break; + case GREATERTHAN: + case GREATERTHAN_OR_EQUALS: + throw new DataFormatException("Upper bound comparator must be < or <=, can not be " + upperBound.getPrefix().getValue()); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ReflectionUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ReflectionUtil.java index b286f89ef08..3cf2c513632 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ReflectionUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ReflectionUtil.java @@ -86,7 +86,7 @@ public class ReflectionUtil { public static Class getGenericCollectionTypeOfMethodParameter(Method theMethod, int theParamIndex) { Class type; Type genericParameterType = theMethod.getGenericParameterTypes()[theParamIndex]; - if (Class.class.equals(genericParameterType)) { + if (Class.class.equals(genericParameterType) || Class.class.equals(genericParameterType.getClass())) { return null; } ParameterizedType collectionType = (ParameterizedType) genericParameterType; diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties index f4999e821d5..6c18ab8b798 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties @@ -57,6 +57,7 @@ ca.uhn.fhir.validation.ValidationResult.noIssuesDetected=No issues detected duri # JPA Messages + ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect.resourceVersionConstraintFailure=The operation has failed with a version constraint failure. This generally means that two clients/threads were trying to update the same resource at the same time, and this request was chosen as the failing request. ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect.resourceIndexedCompositeStringUniqueConstraintFailure=The operation has failed with a unique index constraint failure. This probably means that the operation was trying to create/update a resource that would have resulted in a duplicate value for a unique index. @@ -90,9 +91,13 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulUpdate=Successfully update ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulDeletes=Successfully deleted {0} resource(s) in {1}ms ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidSearchParameter=Unknown search parameter "{0}". Value search parameters for this search are: {1} +ca.uhn.fhir.jpa.dao.BaseSearchParamExtractor.failedToExtractPaths=Failed to extract values from resource using FHIRPath "{0}": {1} + ca.uhn.fhir.jpa.dao.SearchBuilder.invalidQuantityPrefix=Unable to handle quantity prefix "{0}" for value: {1} ca.uhn.fhir.jpa.dao.SearchBuilder.invalidNumberPrefix=Unable to handle number prefix "{0}" for value: {1} +ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4.invalidSearchParamExpression=The expression "{0}" can not be evaluated and may be invalid: {1} + ca.uhn.fhir.jpa.provider.BaseJpaProvider.cantCombintAtAndSince=Unable to combine _at and _since parameters for history operation ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateConceptMapUrl=Can not create multiple ConceptMap resources with ConceptMap.url "{0}", already have one with resource ID: {1} diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java index 000e0eb2a48..fa6869feb40 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.client.impl; * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -743,6 +743,7 @@ public class GenericClient extends BaseClient implements IGenericClient { private Class myReturnType; private IPrimitiveType mySince; private Class myType; + private DateRangeParam myAt; @SuppressWarnings("unchecked") @Override @@ -752,6 +753,12 @@ public class GenericClient extends BaseClient implements IGenericClient { return this; } + @Override + public IHistoryTyped at(DateRangeParam theDateRangeParam) { + myAt = theDateRangeParam; + return this; + } + @Override public IHistoryTyped count(Integer theCount) { myCount = theCount; @@ -774,7 +781,7 @@ public class GenericClient extends BaseClient implements IGenericClient { id = null; } - HttpGetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(myContext, resourceName, id, mySince, myCount); + HttpGetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(myContext, resourceName, id, mySince, myCount, myAt); IClientResponseHandler handler; handler = new ResourceResponseHandler(myReturnType, getPreferResponseTypes(myType)); diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HistoryMethodBinding.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HistoryMethodBinding.java index 13e64fb5cf2..8746cdee91c 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HistoryMethodBinding.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/HistoryMethodBinding.java @@ -26,6 +26,8 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Date; +import ca.uhn.fhir.rest.param.DateParam; +import ca.uhn.fhir.rest.param.DateRangeParam; import org.hl7.fhir.instance.model.api.*; import ca.uhn.fhir.context.FhirContext; @@ -96,7 +98,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { } String historyId = id != null ? id.getIdPart() : null; - HttpGetClientInvocation retVal = createHistoryInvocation(getContext(), resourceName, historyId, null, null); + HttpGetClientInvocation retVal = createHistoryInvocation(getContext(), resourceName, historyId, null, null, null); if (theArgs != null) { for (int idx = 0; idx < theArgs.length; idx++) { @@ -108,7 +110,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { return retVal; } - public static HttpGetClientInvocation createHistoryInvocation(FhirContext theContext, String theResourceName, String theId, IPrimitiveType theSince, Integer theLimit) { + public static HttpGetClientInvocation createHistoryInvocation(FhirContext theContext, String theResourceName, String theId, IPrimitiveType theSince, Integer theLimit, DateRangeParam theAt) { StringBuilder b = new StringBuilder(); if (theResourceName != null) { b.append(theResourceName); @@ -129,8 +131,18 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { } if (theLimit != null) { b.append(haveParam ? '&' : '?'); + haveParam = true; b.append(Constants.PARAM_COUNT).append('=').append(theLimit); } + if (theAt != null) { + for (DateParam next : theAt.getValuesAsQueryTokens()) { + b.append(haveParam ? '&' : '?'); + haveParam = true; + b.append(Constants.PARAM_AT); + b.append("="); + b.append(next.getValueAsQueryToken(theContext)); + } + } HttpGetClientInvocation retVal = new HttpGetClientInvocation(theContext, b.toString()); return retVal; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamExtractor.java index 258b5bf291a..92216d40b71 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamExtractor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseSearchParamExtractor.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.dao; * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.dao; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.FhirTerser; import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang3.ObjectUtils; @@ -47,6 +48,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor private DaoConfig myDaoConfig; @Autowired private ISearchParamRegistry mySearchParamRegistry; + public BaseSearchParamExtractor() { super(); } @@ -73,31 +75,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor return refs; } - protected List extractValues(String thePaths, IBaseResource theResource) { - List values = new ArrayList(); - String[] nextPathsSplit = SPLIT.split(thePaths); - FhirTerser t = myContext.newTerser(); - for (String nextPath : nextPathsSplit) { - String nextPathTrimmed = nextPath.trim(); - try { - List allValues = t.getValues(theResource, nextPathTrimmed); - for (Object next : allValues) { - if (next instanceof IBaseExtension) { - IBaseDatatype value = ((IBaseExtension) next).getValue(); - if (value != null) { - values.add(value); - } - } else { - values.add(next); - } - } - } catch (Exception e) { - RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource); - ourLog.warn("Failed to index values from path[{}] in resource type[{}]: {}", new Object[] {nextPathTrimmed, def.getName(), e.toString(), e}); - } - } - return values; - } + protected abstract List extractValues(String thePaths, IBaseResource theResource); protected FhirContext getContext() { return myContext; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java index 9c6a156cefc..57fed5b50f1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java @@ -20,9 +20,9 @@ import java.util.*; * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -144,6 +144,7 @@ public class DaoConfig { private boolean myExpungeEnabled; private int myReindexThreadCount; private Set myBundleTypesAllowedForStorage; + private boolean myValidateSearchParameterExpressionsOnSave = true; /** * Constructor @@ -786,7 +787,6 @@ public class DaoConfig { this.myAllowContainsSearches = theAllowContainsSearches; } - /** * If set to true (default is false) the server will allow * resources to have references to external servers. For example if this server is @@ -1188,6 +1188,34 @@ public class DaoConfig { myUniqueIndexesEnabled = theUniqueIndexesEnabled; } + /** + * If true (default is true), before allowing a + * SearchParameter resource to be stored (create, update, etc.) the + * expression will be performed against an empty resource to ensure that + * the FHIRPath executor is able to process it. + *

+ * This should proabably always be set to true, but is configurable + * in order to support some unit tests. + *

+ */ + public boolean isValidateSearchParameterExpressionsOnSave() { + return myValidateSearchParameterExpressionsOnSave; + } + + /** + * If true (default is true), before allowing a + * SearchParameter resource to be stored (create, update, etc.) the + * expression will be performed against an empty resource to ensure that + * the FHIRPath executor is able to process it. + *

+ * This should proabably always be set to true, but is configurable + * in order to support some unit tests. + *

+ */ + public void setValidateSearchParameterExpressionsOnSave(boolean theValidateSearchParameterExpressionsOnSave) { + myValidateSearchParameterExpressionsOnSave = theValidateSearchParameterExpressionsOnSave; + } + /** * Do not call this method, it exists only for legacy reasons. It * will be removed in a future version. Configure the page size on your diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSearchParameterDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSearchParameterDstu2.java index 50bf987cbe1..7c4e8bcb7bf 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSearchParameterDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSearchParameterDstu2.java @@ -29,14 +29,8 @@ import ca.uhn.fhir.model.dstu2.resource.SearchParameter; import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum; import ca.uhn.fhir.model.dstu2.valueset.SearchParamTypeEnum; import ca.uhn.fhir.model.primitive.BoundCodeDt; -import ca.uhn.fhir.model.primitive.CodeDt; -import org.apache.commons.lang3.time.DateUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -83,7 +77,7 @@ public class FhirResourceDaoSearchParameterDstu2 extends FhirResourceDaoDstu2 extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) { - HashSet retVal = new HashSet(); + HashSet retVal = new HashSet<>(); Collection searchParams = getSearchParams(theResource); for (RuntimeSearchParam nextSpDef : searchParams) { @@ -142,7 +149,7 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen /* * (non-Javadoc) - * + * * @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamNumber(ca.uhn.fhir.jpa.entity.ResourceTable, * ca.uhn.fhir.model.api.IResource) */ @@ -196,7 +203,7 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen * org.unitsofmeasurement.quantity.Quantity>) * UCUMFormat.getCaseInsensitiveInstance().parse(nextValue.getCode().getValue(), null); if * (unit.isCompatible(UCUM.DAY)) { - * + * * @SuppressWarnings("unchecked") PhysicsUnit timeUnit = * (PhysicsUnit