diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BaseDateTimeDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BaseDateTimeDt.java index 4c7c5c105b5..c02c94c0863 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BaseDateTimeDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BaseDateTimeDt.java @@ -84,9 +84,7 @@ public abstract class BaseDateTimeDt extends BasePrimitive { */ public BaseDateTimeDt(String theString) { setValueAsString(theString); - if (isPrecisionAllowed(getPrecision()) == false) { - throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support " + getPrecision() + " precision): " + theString); - } + validatePrecisionAndThrowDataFormatException(theString, getPrecision()); } private void clearTimeZone() { @@ -158,11 +156,74 @@ public abstract class BaseDateTimeDt extends BasePrimitive { return b.toString(); } + /** + * Returns the month with 1-index, e.g. 1=the first day of the month + */ + public Integer getDay() { + return getFieldValue(Calendar.DAY_OF_MONTH); + } + /** * Returns the default precision for the given datatype */ protected abstract TemporalPrecisionEnum getDefaultPrecisionForDatatype(); + private Integer getFieldValue(int theField) { + if (getValue() == null) { + return null; + } + Calendar cal = getValueAsCalendar(); + return cal.get(theField); + } + + /** + * Returns the hour of the day in a 24h clock, e.g. 13=1pm + */ + public Integer getHour() { + return getFieldValue(Calendar.HOUR_OF_DAY); + } + + /** + * Returns the milliseconds within the current second. + *

+ * Note that this method returns the + * same value as {@link #getNanos()} but with less precision. + *

+ */ + public Integer getMillis() { + return getFieldValue(Calendar.MILLISECOND); + } + + /** + * Returns the minute of the hour in the range 0-59 + */ + public Integer getMinute() { + return getFieldValue(Calendar.MINUTE); + } + + /** + * Returns the month with 0-index, e.g. 0=January + */ + public Integer getMonth() { + return getFieldValue(Calendar.MONTH); + } + + /** + * Returns the nanoseconds within the current second + *

+ * Note that this method returns the + * same value as {@link #getMillis()} but with more precision. + *

+ */ + public Long getNanos() { + if (isBlank(myFractionalSeconds)) { + return null; + } + String retVal = StringUtils.rightPad(myFractionalSeconds, 9, '0'); + retVal = retVal.substring(0, 9); + return Long.parseLong(retVal); + } + private int getOffsetIndex(String theValueString) { int plusIndex = theValueString.indexOf('+', 16); int minusIndex = theValueString.indexOf('-', 16); @@ -189,6 +250,13 @@ public abstract class BaseDateTimeDt extends BasePrimitive { return myPrecision; } + /** + * Returns the second of the minute in the range 0-59 + */ + public Integer getSecond() { + return getFieldValue(Calendar.SECOND); + } + /** * Returns the TimeZone associated with this dateTime's value. May return null if no timezone was * supplied. @@ -217,10 +285,17 @@ public abstract class BaseDateTimeDt extends BasePrimitive { return cal; } + /** + * Returns the year, e.g. 2015 + */ + public Integer getYear() { + return getFieldValue(Calendar.YEAR); + } + /** * To be implemented by subclasses to indicate whether the given precision is allowed by this type */ - abstract boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision); + protected abstract boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision); /** * Returns true if the timezone is set to GMT-0:00 (Z) @@ -285,7 +360,7 @@ public abstract class BaseDateTimeDt extends BasePrimitive { cal.set(Calendar.DAY_OF_MONTH, parseInt(value, value.substring(8, 10), 1, actualMaximum)); precision = TemporalPrecisionEnum.DAY; if (length > 10) { - validateLengthIsAtLeast(value, 17); + validateLengthIsAtLeast(value, 16); validateCharAtIndexIs(value, 10, 'T'); // yyyy-mm-ddThh:mm:ss int offsetIdx = getOffsetIndex(value); String time; @@ -352,6 +427,10 @@ public abstract class BaseDateTimeDt extends BasePrimitive { myFractionalSeconds = ""; } + if (precision == TemporalPrecisionEnum.MINUTE) { + validatePrecisionAndThrowDataFormatException(value, precision); + } + setPrecision(precision); return cal.getTime(); @@ -372,6 +451,92 @@ public abstract class BaseDateTimeDt extends BasePrimitive { return retVal; } + /** + * Sets the month with 1-index, e.g. 1=the first day of the month + */ + public BaseDateTimeDt setDay(int theDay) { + setFieldValue(Calendar.DAY_OF_MONTH, theDay, null, 0, 31); + return this; + } + + private void setFieldValue(int theField, int theValue, String theFractionalSeconds, int theMinimum, int theMaximum) { + validateValueInRange(theValue, theMinimum, theMaximum); + Calendar cal; + if (getValue() == null) { + cal = new GregorianCalendar(0, 0, 0); + } else { + cal = getValueAsCalendar(); + } + if (theField != -1) { + cal.set(theField, theValue); + } + if (theFractionalSeconds != null) { + myFractionalSeconds = theFractionalSeconds; + } else if (theField == Calendar.MILLISECOND) { + myFractionalSeconds = StringUtils.leftPad(Integer.toString(theValue), 3, '0'); + } + super.setValue(cal.getTime()); + } + + /** + * Sets the hour of the day in a 24h clock, e.g. 13=1pm + */ + public BaseDateTimeDt setHour(int theHour) { + setFieldValue(Calendar.HOUR_OF_DAY, theHour, null, 0, 23); + return this; + } + + /** + * Sets the milliseconds within the current second. + *

+ * Note that this method sets the + * same value as {@link #setNanos(long)} but with less precision. + *

+ */ + public BaseDateTimeDt setMillis(int theMillis) { + setFieldValue(Calendar.MILLISECOND, theMillis, null, 0, 999); + return this; + } + + /** + * Sets the minute of the hour in the range 0-59 + */ + public BaseDateTimeDt setMinute(int theMinute) { + setFieldValue(Calendar.MINUTE, theMinute, null, 0, 59); + return this; + } + + /** + * Sets the month with 0-index, e.g. 0=January + */ + public BaseDateTimeDt setMonth(int theMonth) { + setFieldValue(Calendar.MONTH, theMonth, null, 0, 11); + return this; + } + + /** + * Sets the nanoseconds within the current second + *

+ * Note that this method sets the + * same value as {@link #setMillis(int)} but with more precision. + *

+ */ + public BaseDateTimeDt setNanos(long theNanos) { + validateValueInRange(theNanos, 0, NANOS_PER_SECOND-1); + String fractionalSeconds = StringUtils.leftPad(Long.toString(theNanos), 9, '0'); + + // Strip trailing 0s + for (int i = fractionalSeconds.length(); i > 0; i--) { + if (fractionalSeconds.charAt(i-1) != '0') { + fractionalSeconds = fractionalSeconds.substring(0, i); + break; + } + } + int millis = (int)(theNanos / NANOS_PER_MILLIS); + setFieldValue(Calendar.MILLISECOND, millis, fractionalSeconds, 0, 999); + return this; + } + /** * Sets the precision for this datatype * @@ -386,6 +551,14 @@ public abstract class BaseDateTimeDt extends BasePrimitive { return this; } + /** + * Sets the second of the minute in the range 0-59 + */ + public BaseDateTimeDt setSecond(int theSecond) { + setFieldValue(Calendar.SECOND, theSecond, null, 0, 59); + return this; + } + private BaseDateTimeDt setTimeZone(String theWholeValue, String theValue) { if (isBlank(theValue)) { @@ -465,6 +638,14 @@ public abstract class BaseDateTimeDt extends BasePrimitive { super.setValueAsString(theValue); } + /** + * Sets the year, e.g. 2015 + */ + public BaseDateTimeDt setYear(int theYear) { + setFieldValue(Calendar.YEAR, theYear, null, 0, 9999); + return this; + } + private void throwBadDateFormat(String theValue) { throw new DataFormatException("Invalid date/time format: \"" + theValue + "\""); } @@ -531,189 +712,16 @@ public abstract class BaseDateTimeDt extends BasePrimitive { } } - /** - * Returns the year, e.g. 2015 - */ - public Integer getYear() { - return getFieldValue(Calendar.YEAR); - } - - /** - * Returns the month with 0-index, e.g. 0=January - */ - public Integer getMonth() { - return getFieldValue(Calendar.MONTH); - } - - /** - * Returns the month with 1-index, e.g. 1=the first day of the month - */ - public Integer getDay() { - return getFieldValue(Calendar.DAY_OF_MONTH); - } - - /** - * Returns the hour of the day in a 24h clock, e.g. 13=1pm - */ - public Integer getHour() { - return getFieldValue(Calendar.HOUR_OF_DAY); - } - - /** - * Returns the minute of the hour in the range 0-59 - */ - public Integer getMinute() { - return getFieldValue(Calendar.MINUTE); - } - - /** - * Returns the second of the minute in the range 0-59 - */ - public Integer getSecond() { - return getFieldValue(Calendar.SECOND); - } - - /** - * Returns the milliseconds within the current second. - *

- * Note that this method returns the - * same value as {@link #getNanos()} but with less precision. - *

- */ - public Integer getMillis() { - return getFieldValue(Calendar.MILLISECOND); - } - - /** - * Returns the nanoseconds within the current second - *

- * Note that this method returns the - * same value as {@link #getMillis()} but with more precision. - *

- */ - public Long getNanos() { - if (isBlank(myFractionalSeconds)) { - return null; - } - String retVal = StringUtils.rightPad(myFractionalSeconds, 9, '0'); - retVal = retVal.substring(0, 9); - return Long.parseLong(retVal); - } - - /** - * Sets the year, e.g. 2015 - */ - public BaseDateTimeDt setYear(int theYear) { - setFieldValue(Calendar.YEAR, theYear, null, 0, 9999); - return this; - } - - /** - * Sets the month with 0-index, e.g. 0=January - */ - public BaseDateTimeDt setMonth(int theMonth) { - setFieldValue(Calendar.MONTH, theMonth, null, 0, 11); - return this; - } - - /** - * Sets the month with 1-index, e.g. 1=the first day of the month - */ - public BaseDateTimeDt setDay(int theDay) { - setFieldValue(Calendar.DAY_OF_MONTH, theDay, null, 0, 31); - return this; - } - - /** - * Sets the hour of the day in a 24h clock, e.g. 13=1pm - */ - public BaseDateTimeDt setHour(int theHour) { - setFieldValue(Calendar.HOUR_OF_DAY, theHour, null, 0, 23); - return this; - } - - /** - * Sets the minute of the hour in the range 0-59 - */ - public BaseDateTimeDt setMinute(int theMinute) { - setFieldValue(Calendar.MINUTE, theMinute, null, 0, 59); - return this; - } - - /** - * Sets the second of the minute in the range 0-59 - */ - public BaseDateTimeDt setSecond(int theSecond) { - setFieldValue(Calendar.SECOND, theSecond, null, 0, 59); - return this; - } - - /** - * Sets the milliseconds within the current second. - *

- * Note that this method sets the - * same value as {@link #setNanos(long)} but with less precision. - *

- */ - public BaseDateTimeDt setMillis(int theMillis) { - setFieldValue(Calendar.MILLISECOND, theMillis, null, 0, 999); - return this; - } - - /** - * Sets the nanoseconds within the current second - *

- * Note that this method sets the - * same value as {@link #setMillis(int)} but with more precision. - *

- */ - public BaseDateTimeDt setNanos(long theNanos) { - validateValueInRange(theNanos, 0, NANOS_PER_SECOND-1); - String fractionalSeconds = StringUtils.leftPad(Long.toString(theNanos), 9, '0'); - - // Strip trailing 0s - for (int i = fractionalSeconds.length(); i > 0; i--) { - if (fractionalSeconds.charAt(i-1) != '0') { - fractionalSeconds = fractionalSeconds.substring(0, i); - break; - } - } - int millis = (int)(theNanos / NANOS_PER_MILLIS); - setFieldValue(Calendar.MILLISECOND, millis, fractionalSeconds, 0, 999); - return this; - } - - private void setFieldValue(int theField, int theValue, String theFractionalSeconds, int theMinimum, int theMaximum) { - validateValueInRange(theValue, theMinimum, theMaximum); - Calendar cal; - if (getValue() == null) { - cal = new GregorianCalendar(0, 0, 0); - } else { - cal = getValueAsCalendar(); - } - if (theField != -1) { - cal.set(theField, theValue); - } - if (theFractionalSeconds != null) { - myFractionalSeconds = theFractionalSeconds; - } else if (theField == Calendar.MILLISECOND) { - myFractionalSeconds = StringUtils.leftPad(Integer.toString(theValue), 3, '0'); - } - super.setValue(cal.getTime()); - } - private void validateValueInRange(long theValue, long theMinimum, long theMaximum) { if (theValue < theMinimum || theValue > theMaximum) { throw new IllegalArgumentException("Value " + theValue + " is not between allowable range: " + theMinimum + " - " + theMaximum); } } - private Integer getFieldValue(int theField) { - if (getValue() == null) { - return null; + private void validatePrecisionAndThrowDataFormatException(String theValue, TemporalPrecisionEnum thePrecision) { + if (isPrecisionAllowed(thePrecision) == false) { + throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support " + thePrecision + " precision): " + theValue); } - Calendar cal = getValueAsCalendar(); - return cal.get(theField); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/DateDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/DateDt.java index 46c55778fe5..d407f41b83e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/DateDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/DateDt.java @@ -133,7 +133,7 @@ public class DateDt extends BaseDateTimeDt { } @Override - boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision) { + protected boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision) { switch (thePrecision) { case YEAR: case MONTH: diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/DateTimeDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/DateTimeDt.java index b3a2cbf1c71..3d070243289 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/DateTimeDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/DateTimeDt.java @@ -105,7 +105,7 @@ public class DateTimeDt extends BaseDateTimeDt { } @Override - boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision) { + protected boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision) { switch (thePrecision) { case YEAR: case MONTH: diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/InstantDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/InstantDt.java index 850c1f37f13..8d16af910b3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/InstantDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/InstantDt.java @@ -157,7 +157,7 @@ public class InstantDt extends BaseDateTimeDt { } @Override - boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision) { + protected boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision) { switch (thePrecision) { case SECOND: case MILLI: diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateParam.java index ba863284f64..c0ecb5133aa 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateParam.java @@ -34,17 +34,18 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.IQueryParameterOr; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; -import ca.uhn.fhir.model.primitive.DateDt; -import ca.uhn.fhir.model.primitive.DateTimeDt; -import ca.uhn.fhir.model.primitive.InstantDt; +import ca.uhn.fhir.model.primitive.*; import ca.uhn.fhir.rest.method.QualifiedParamList; +import ca.uhn.fhir.rest.param.DateParam.DateParamDateTimeHolder; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.ValidateUtil; @SuppressWarnings("deprecation") public class DateParam extends BaseParamWithPrefix implements /*IQueryParameterType , */IQueryParameterOr { - private final DateTimeDt myValue = new DateTimeDt(); + private static final long serialVersionUID = 1L; + + private final DateParamDateTimeHolder myValue = new DateParamDateTimeHolder(); /** * Constructor @@ -223,6 +224,11 @@ public class DateParam extends BaseParamWithPrefix implements /*IQuer return null; } + @Override + public List getValuesAsQueryTokens() { + return Collections.singletonList(this); + } + /** * Returns true if no date/time is specified. Note that this method does not check the comparator, so a * QualifiedDateParam with only a comparator and no date/time is considered empty. @@ -267,6 +273,20 @@ public class DateParam extends BaseParamWithPrefix implements /*IQuer } } + @Override + public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, QualifiedParamList theParameters) { + setMissing(null); + setPrefix(null); + setValueAsString(null); + + if (theParameters.size() == 1) { + setValueAsString(theParameters.get(0)); + } else if (theParameters.size() > 1) { + throw new InvalidRequestException("This server does not support multi-valued dates for this paramater: " + theParameters); + } + + } + private ParamPrefixEnum toPrefix(QuantityCompararatorEnum theComparator) { if (theComparator != null) { return ParamPrefixEnum.forDstu1Value(theComparator.getCode()); @@ -282,23 +302,17 @@ public class DateParam extends BaseParamWithPrefix implements /*IQuer return b.build(); } - @Override - public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, QualifiedParamList theParameters) { - setMissing(null); - setPrefix(null); - setValueAsString(null); - - if (theParameters.size() == 1) { - setValueAsString(theParameters.get(0)); - } else if (theParameters.size() > 1) { - throw new InvalidRequestException("This server does not support multi-valued dates for this paramater: " + theParameters); + public class DateParamDateTimeHolder extends BaseDateTimeDt { + @Override + protected TemporalPrecisionEnum getDefaultPrecisionForDatatype() { + return TemporalPrecisionEnum.SECOND; + } + + @Override + protected boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision) { + return true; } - - } - @Override - public List getValuesAsQueryTokens() { - return Collections.singletonList(this); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java index 110d0703276..a723bd9682a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java @@ -43,7 +43,16 @@ import org.hl7.fhir.instance.model.api.*; import com.google.common.collect.*; -import ca.uhn.fhir.context.*; +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.RuntimeChildChoiceDefinition; +import ca.uhn.fhir.context.RuntimeChildResourceDefinition; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao; import ca.uhn.fhir.jpa.entity.*; @@ -1048,7 +1057,7 @@ public class SearchBuilder implements ISearchBuilder { if (!isBlank(unitsValue)) { code = theBuilder.equal(theFrom.get("myUnits"), unitsValue); } - + cmpValue = ObjectUtils.defaultIfNull(cmpValue, ParamPrefixEnum.EQUAL); final Expression path = theFrom.get("myValue"); String invalidMessageName = "invalidQuantityPrefix"; @@ -1740,6 +1749,39 @@ public class SearchBuilder implements ISearchBuilder { } private void searchForIdsWithAndOr(String theResourceName, String theParamName, List> theAndOrParams) { + + for (int andListIdx = 0; andListIdx < theAndOrParams.size(); andListIdx++) { + List nextOrList = theAndOrParams.get(andListIdx); + + for (int orListIdx = 0; orListIdx < nextOrList.size(); orListIdx++) { + IQueryParameterType nextOr = nextOrList.get(orListIdx); + boolean hasNoValue = false; + if (nextOr.getMissing() != null) { + continue; + } + if (nextOr instanceof QuantityParam) { + if (isBlank(((QuantityParam) nextOr).getValueAsString())) { + hasNoValue = true; + } + } + + if (hasNoValue) { + ourLog.debug("Ignoring empty parameter: {}", theParamName); + nextOrList.remove(orListIdx); + orListIdx--; + } + } + + if (nextOrList.isEmpty()) { + theAndOrParams.remove(andListIdx); + andListIdx--; + } + } + + if (theAndOrParams.isEmpty()) { + return; + } + if (theParamName.equals(BaseResource.SP_RES_ID)) { addPredicateResourceId(theAndOrParams); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java index e9cb2b3080c..25642e089aa 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java @@ -68,7 +68,6 @@ import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType; import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus; import org.hl7.fhir.dstu3.model.Substance; import org.hl7.fhir.dstu3.model.Task; -import org.hl7.fhir.dstu3.model.TemporalPrecisionEnum; import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -92,6 +91,7 @@ import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamUri; import ca.uhn.fhir.jpa.entity.ResourceLink; import ca.uhn.fhir.model.api.Include; +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.SortOrderEnum; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java index f7c721fa503..5ee6a4d6d3b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java @@ -21,22 +21,10 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketTimeoutException; -import java.net.URLEncoder; +import java.io.*; +import java.net.*; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; +import java.util.*; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; @@ -44,89 +32,29 @@ import org.apache.commons.lang3.Validate; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPatch; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.entity.ByteArrayEntity; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.StringEntity; +import org.apache.http.client.methods.*; +import org.apache.http.entity.*; import org.apache.http.message.BasicNameValuePair; -import org.hl7.fhir.dstu3.model.AuditEvent; -import org.hl7.fhir.dstu3.model.BaseResource; -import org.hl7.fhir.dstu3.model.Basic; -import org.hl7.fhir.dstu3.model.Binary; -import org.hl7.fhir.dstu3.model.Bundle; -import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; -import org.hl7.fhir.dstu3.model.Bundle.BundleLinkComponent; -import org.hl7.fhir.dstu3.model.Bundle.BundleType; -import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; -import org.hl7.fhir.dstu3.model.Bundle.SearchEntryMode; -import org.hl7.fhir.dstu3.model.CodeSystem; -import org.hl7.fhir.dstu3.model.CodeType; -import org.hl7.fhir.dstu3.model.Coding; -import org.hl7.fhir.dstu3.model.Condition; -import org.hl7.fhir.dstu3.model.DateTimeType; -import org.hl7.fhir.dstu3.model.DateType; -import org.hl7.fhir.dstu3.model.Device; -import org.hl7.fhir.dstu3.model.ProcedureRequest; -import org.hl7.fhir.dstu3.model.DocumentManifest; -import org.hl7.fhir.dstu3.model.DocumentReference; -import org.hl7.fhir.dstu3.model.Encounter; +import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.Bundle.*; import org.hl7.fhir.dstu3.model.Encounter.EncounterLocationComponent; import org.hl7.fhir.dstu3.model.Encounter.EncounterStatus; import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; -import org.hl7.fhir.dstu3.model.Extension; -import org.hl7.fhir.dstu3.model.IdType; -import org.hl7.fhir.dstu3.model.ImagingStudy; -import org.hl7.fhir.dstu3.model.InstantType; -import org.hl7.fhir.dstu3.model.IntegerType; -import org.hl7.fhir.dstu3.model.Location; -import org.hl7.fhir.dstu3.model.Medication; -import org.hl7.fhir.dstu3.model.MedicationAdministration; -import org.hl7.fhir.dstu3.model.MedicationRequest; -import org.hl7.fhir.dstu3.model.Meta; import org.hl7.fhir.dstu3.model.Narrative.NarrativeStatus; -import org.hl7.fhir.dstu3.model.Observation; import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; -import org.hl7.fhir.dstu3.model.OperationOutcome; -import org.hl7.fhir.dstu3.model.Organization; -import org.hl7.fhir.dstu3.model.Parameters; -import org.hl7.fhir.dstu3.model.Patient; -import org.hl7.fhir.dstu3.model.Period; -import org.hl7.fhir.dstu3.model.Practitioner; -import org.hl7.fhir.dstu3.model.Quantity; -import org.hl7.fhir.dstu3.model.Questionnaire; import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType; -import org.hl7.fhir.dstu3.model.QuestionnaireResponse; -import org.hl7.fhir.dstu3.model.Reference; -import org.hl7.fhir.dstu3.model.StringType; -import org.hl7.fhir.dstu3.model.StructureDefinition; -import org.hl7.fhir.dstu3.model.Subscription; import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType; import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus; -import org.hl7.fhir.dstu3.model.TemporalPrecisionEnum; -import org.hl7.fhir.dstu3.model.UnsignedIntType; -import org.hl7.fhir.dstu3.model.ValueSet; -import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; +import org.hl7.fhir.instance.model.api.*; +import org.junit.*; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; -import org.springframework.transaction.support.TransactionCallbackWithoutResult; import com.google.common.collect.Lists; import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.SearchParameterMap; import ca.uhn.fhir.jpa.entity.Search; +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.parser.IParser; @@ -135,18 +63,9 @@ import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.client.IGenericClient; import ca.uhn.fhir.rest.gclient.StringClientParam; -import ca.uhn.fhir.rest.param.DateParam; -import ca.uhn.fhir.rest.param.DateRangeParam; -import ca.uhn.fhir.rest.param.ParamPrefixEnum; -import ca.uhn.fhir.rest.param.StringAndListParam; -import ca.uhn.fhir.rest.param.StringOrListParam; -import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.server.Constants; -import ca.uhn.fhir.rest.server.IBundleProvider; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; -import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; -import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ca.uhn.fhir.rest.server.exceptions.*; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.UrlUtil; @@ -178,14 +97,41 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { myDaoConfig.setAllowMultipleDelete(true); } - + @Test + public void testSearchWithEmptyParameter() throws Exception { + Observation obs= new Observation(); + obs.setStatus(ObservationStatus.FINAL); + obs.getCode().addCoding().setSystem("foo").setCode("bar"); + ourClient.create().resource(obs).execute(); + + testSearchWithEmptyParameter("/Observation?value-quantity="); + testSearchWithEmptyParameter("/Observation?code=bar&value-quantity="); + testSearchWithEmptyParameter("/Observation?value-date="); + testSearchWithEmptyParameter("/Observation?code=bar&value-date="); + testSearchWithEmptyParameter("/Observation?value-concept="); + testSearchWithEmptyParameter("/Observation?code=bar&value-concept="); + } + + private void testSearchWithEmptyParameter(String url) throws IOException, ClientProtocolException { + HttpGet get = new HttpGet(ourServerBase + url); + CloseableHttpResponse resp = ourHttpClient.execute(get); + try { + assertEquals(200, resp.getStatusLine().getStatusCode()); + String respString = IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8); + Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, respString); + assertEquals(1, bundle.getEntry().size()); + } finally { + IOUtils.closeQuietly(resp.getEntity().getContent()); + } + } + @Test public void testSearchWithMissingDate2() throws Exception { MedicationRequest mr1 = new MedicationRequest(); mr1.getCategory().addCoding().setSystem("urn:medicationroute").setCode("oral"); mr1.addDosageInstruction().getTiming().addEventElement().setValueAsString("2017-01-01"); IIdType id1 = myMedicationRequestDao.create(mr1).getId().toUnqualifiedVersionless(); - + MedicationRequest mr2 = new MedicationRequest(); mr2.getCategory().addCoding().setSystem("urn:medicationroute").setCode("oral"); IIdType id2 = myMedicationRequestDao.create(mr2).getId().toUnqualifiedVersionless(); @@ -195,38 +141,34 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { try { assertEquals(200, resp.getStatusLine().getStatusCode()); Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8)); - + List ids = toUnqualifiedVersionlessIdValues(bundle); assertThat(ids, contains(id1.getValue())); } finally { IOUtils.closeQuietly(resp); } - - } - + @Test public void testEverythingWithOnlyPatient() { Patient p = new Patient(); p.setActive(true); IIdType id = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless(); - + myFhirCtx.getRestfulClientFactory().setSocketTimeout(300 * 1000); - + Bundle response = ourClient - .operation() - .onInstance(id) - .named("everything") - .withNoParameters(Parameters.class) - .returnResourceType(Bundle.class) - .execute(); - + .operation() + .onInstance(id) + .named("everything") + .withNoParameters(Parameters.class) + .returnResourceType(Bundle.class) + .execute(); + assertEquals(1, response.getEntry().size()); } - - @Test public void testSaveAndRetrieveResourceWithExtension() { Patient nextPatient = new Patient(); @@ -239,7 +181,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { ourClient.update().resource(nextPatient).execute(); Patient p = ourClient.read().resource(Patient.class).withId("B").execute(); - + String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(p); ourLog.info(encoded); diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/model/primitive/BaseDateTimeDtDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/model/primitive/BaseDateTimeDtDstu2Test.java index d681759fac5..695343bb7a0 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/model/primitive/BaseDateTimeDtDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/model/primitive/BaseDateTimeDtDstu2Test.java @@ -569,6 +569,28 @@ public class BaseDateTimeDtDstu2Test { dt.setValueAsString("201302"); } + @Test + public void testParseMinuteShouldFail() throws DataFormatException { + DateTimeDt dt = new DateTimeDt(); + try { + dt.setValueAsString("2013-02-03T11:22"); + fail(); + } catch (DataFormatException e) { + assertEquals(e.getMessage(), "Invalid date/time string (datatype DateTimeDt does not support MINUTE precision): 2013-02-03T11:22"); + } + } + + @Test + public void testParseMinuteZuluShouldFail() throws DataFormatException { + DateTimeDt dt = new DateTimeDt(); + try { + dt.setValueAsString("2013-02-03T11:22Z"); + fail(); + } catch (DataFormatException e) { + assertEquals(e.getMessage(), "Invalid date/time string (datatype DateTimeDt does not support MINUTE precision): 2013-02-03T11:22Z"); + } + } + @Test public void testParseSecond() throws DataFormatException { DateTimeDt dt = new DateTimeDt(); @@ -582,7 +604,7 @@ public class BaseDateTimeDtDstu2Test { } @Test - public void testParseSecondulu() throws DataFormatException { + public void testParseSecondZulu() throws DataFormatException { DateTimeDt dt = new DateTimeDt(); dt.setValueAsString("2013-02-03T11:22:33Z"); diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/param/DateParamTest.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/param/DateParamTest.java index 4aaf97c2c57..93d56980ecb 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/param/DateParamTest.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/param/DateParamTest.java @@ -1,6 +1,10 @@ package ca.uhn.fhir.rest.param; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import java.util.Date; import java.util.TimeZone; @@ -19,21 +23,21 @@ public class DateParamTest { public void testConstructors() { new DateParam(); new DateParam("2011-01-02"); - new DateParam(ParamPrefixEnum.GREATERTHAN,new Date()); - new DateParam(ParamPrefixEnum.GREATERTHAN,new DateTimeDt("2011-01-02")); - new DateParam(ParamPrefixEnum.GREATERTHAN,InstantDt.withCurrentTime()); - new DateParam(ParamPrefixEnum.GREATERTHAN,"2011-01-02"); + new DateParam(ParamPrefixEnum.GREATERTHAN, new Date()); + new DateParam(ParamPrefixEnum.GREATERTHAN, new DateTimeDt("2011-01-02")); + new DateParam(ParamPrefixEnum.GREATERTHAN, InstantDt.withCurrentTime()); + new DateParam(ParamPrefixEnum.GREATERTHAN, "2011-01-02"); - new DateParam(QuantityCompararatorEnum.GREATERTHAN,new Date()); - new DateParam(QuantityCompararatorEnum.GREATERTHAN,new DateTimeDt("2011-01-02")); - new DateParam(QuantityCompararatorEnum.GREATERTHAN,InstantDt.withCurrentTime()); - new DateParam(QuantityCompararatorEnum.GREATERTHAN,"2011-01-02"); + new DateParam(QuantityCompararatorEnum.GREATERTHAN, new Date()); + new DateParam(QuantityCompararatorEnum.GREATERTHAN, new DateTimeDt("2011-01-02")); + new DateParam(QuantityCompararatorEnum.GREATERTHAN, InstantDt.withCurrentTime()); + new DateParam(QuantityCompararatorEnum.GREATERTHAN, "2011-01-02"); } - + @Test public void testParse() { Date date = new Date(); - + DateParam param = new DateParam(); param.setValueAsString("gt2016-06-09T20:38:14.591-05:00"); @@ -48,4 +52,56 @@ public class DateParamTest { assertEquals("2016-06-09T21:38:14.591-04:00", dt.getValueAsString()); } + @Test + public void testParseMinutePrecision() { + DateParam param = new DateParam(); + param.setValueAsString("2016-06-09T20:38Z"); + + assertEquals(null, param.getPrefix()); + assertEquals("2016-06-09T20:38Z", param.getValueAsString()); + + ourLog.info("PRE: " + param.getValue()); + ourLog.info("PRE: " + param.getValue().getTime()); + + InstantDt dt = new InstantDt(new Date(param.getValue().getTime())); + dt.setTimeZone(TimeZone.getTimeZone("America/Toronto")); + ourLog.info("POST: " + dt.getValue()); + assertEquals("2016-06-09T16:38:00.000-04:00", dt.getValueAsString()); + } + + @Test + public void testParseMinutePrecisionWithoutTimezone() { + DateParam param = new DateParam(); + param.setValueAsString("2016-06-09T20:38"); + + assertEquals(null, param.getPrefix()); + assertEquals("2016-06-09T20:38", param.getValueAsString()); + + ourLog.info("PRE: " + param.getValue()); + ourLog.info("PRE: " + param.getValue().getTime()); + + InstantDt dt = new InstantDt(new Date(param.getValue().getTime())); + dt.setTimeZone(TimeZone.getTimeZone("America/Toronto")); + ourLog.info("POST: " + dt.getValue()); + assertThat(dt.getValueAsString(), startsWith("2016-06-09T")); + assertThat(dt.getValueAsString(), endsWith("8:00.000-04:00")); + } + + @Test + public void testParseMinutePrecisionWithPrefix() { + DateParam param = new DateParam(); + param.setValueAsString("gt2016-06-09T20:38Z"); + + assertEquals(ParamPrefixEnum.GREATERTHAN, param.getPrefix()); + assertEquals("2016-06-09T20:38Z", param.getValueAsString()); + + ourLog.info("PRE: " + param.getValue()); + ourLog.info("PRE: " + param.getValue().getTime()); + + InstantDt dt = new InstantDt(new Date(param.getValue().getTime())); + dt.setTimeZone(TimeZone.getTimeZone("America/Toronto")); + ourLog.info("POST: " + dt.getValue()); + assertEquals("2016-06-09T16:38:00.000-04:00", dt.getValueAsString()); + } + } diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/BaseDateTimeType.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/BaseDateTimeType.java index 2a86454e549..50fcf8d1d29 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/BaseDateTimeType.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/BaseDateTimeType.java @@ -12,6 +12,7 @@ import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.FastDateFormat; +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.parser.DataFormatException; public abstract class BaseDateTimeType extends PrimitiveType { @@ -44,9 +45,7 @@ public abstract class BaseDateTimeType extends PrimitiveType { */ public BaseDateTimeType(Date theDate, TemporalPrecisionEnum thePrecision) { setValue(theDate, thePrecision); - if (isPrecisionAllowed(thePrecision) == false) { - throw new IllegalArgumentException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support " + thePrecision + " precision): " + theDate); - } + validatePrecisionAndThrowDataFormatException(getValueAsString(), getPrecision()); } /** @@ -55,6 +54,7 @@ public abstract class BaseDateTimeType extends PrimitiveType { public BaseDateTimeType(Date theDate, TemporalPrecisionEnum thePrecision, TimeZone theTimeZone) { this(theDate, thePrecision); setTimeZone(theTimeZone); + validatePrecisionAndThrowDataFormatException(getValueAsString(), getPrecision()); } /** @@ -65,12 +65,10 @@ public abstract class BaseDateTimeType extends PrimitiveType { */ public BaseDateTimeType(String theString) { setValueAsString(theString); - if (isPrecisionAllowed(getPrecision()) == false) { - throw new IllegalArgumentException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support " + getPrecision() + " precision): " + theString); - } + validatePrecisionAndThrowDataFormatException(theString, getPrecision()); } - /** + /** * Adds the given amount to the field specified by theField * * @param theField @@ -307,7 +305,7 @@ public abstract class BaseDateTimeType extends PrimitiveType { */ public TimeZone getTimeZone() { if (myTimeZoneZulu) { - return TimeZone.getTimeZone("GMT"); + return TimeZone.getTimeZone("Z"); } return myTimeZone; } @@ -404,7 +402,7 @@ public abstract class BaseDateTimeType extends PrimitiveType { cal.set(Calendar.DAY_OF_MONTH, parseInt(value, value.substring(8, 10), 1, actualMaximum)); precision = TemporalPrecisionEnum.DAY; if (length > 10) { - validateLengthIsAtLeast(value, 17); + validateLengthIsAtLeast(value, 16); validateCharAtIndexIs(value, 10, 'T'); // yyyy-mm-ddThh:mm:ss int offsetIdx = getOffsetIndex(value); String time; @@ -471,6 +469,10 @@ public abstract class BaseDateTimeType extends PrimitiveType { myFractionalSeconds = ""; } + if (precision == TemporalPrecisionEnum.MINUTE) { + validatePrecisionAndThrowDataFormatException(value, precision); + } + myPrecision = precision; return cal.getTime(); @@ -818,6 +820,12 @@ public abstract class BaseDateTimeType extends PrimitiveType { } } + private void validatePrecisionAndThrowDataFormatException(String theValue, TemporalPrecisionEnum thePrecision) { + if (isPrecisionAllowed(thePrecision) == false) { + throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support " + thePrecision + " precision): " + theValue); + } + } + private void validateValueInRange(long theValue, long theMinimum, long theMaximum) { if (theValue < theMinimum || theValue > theMaximum) { throw new IllegalArgumentException("Value " + theValue + " is not between allowable range: " + theMinimum + " - " + theMaximum); diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/DateTimeType.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/DateTimeType.java index 7860ef9b4ec..f450b42f17f 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/DateTimeType.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/DateTimeType.java @@ -36,6 +36,7 @@ import java.util.zip.DataFormatException; import org.apache.commons.lang3.time.DateUtils; +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.api.annotation.DatatypeDef; /** diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/DateType.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/DateType.java index e3f49e65687..1a22c3e5fe3 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/DateType.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/DateType.java @@ -29,18 +29,14 @@ POSSIBILITY OF SUCH DAMAGE. package org.hl7.fhir.dstu3.model; -import java.util.Calendar; - /** * Primitive type "date" in FHIR: any day in a gregorian calendar */ - -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.TimeZone; +import java.util.*; import org.apache.commons.lang3.Validate; +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.api.annotation.DatatypeDef; /** diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/InstantType.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/InstantType.java index ecbb765be34..d74361bb6ad 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/InstantType.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/InstantType.java @@ -31,11 +31,10 @@ POSSIBILITY OF SUCH DAMAGE. */ package org.hl7.fhir.dstu3.model; -import java.util.Calendar; -import java.util.Date; -import java.util.TimeZone; +import java.util.*; import java.util.zip.DataFormatException; +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.api.annotation.DatatypeDef; /** diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/Period.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/Period.java index b10d6f74211..c5fd40e8cee 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/Period.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/Period.java @@ -30,16 +30,14 @@ package org.hl7.fhir.dstu3.model; */ // Generated on Mon, Apr 17, 2017 17:38-0400 for FHIR v3.0.1 +import java.util.Date; +import java.util.List; -import java.util.*; - -import ca.uhn.fhir.model.api.annotation.Child; -import ca.uhn.fhir.model.api.annotation.ChildOrder; -import ca.uhn.fhir.model.api.annotation.Description; -import ca.uhn.fhir.model.api.annotation.DatatypeDef; -import ca.uhn.fhir.model.api.annotation.Block; -import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.instance.model.api.ICompositeType; + +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; +import ca.uhn.fhir.model.api.annotation.*; /** * A time period defined by a start and end date and optionally time. */ diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/TemporalPrecisionEnum.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/TemporalPrecisionEnum.java deleted file mode 100644 index 5b5bca1ae64..00000000000 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/TemporalPrecisionEnum.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.hl7.fhir.dstu3.model; - -import java.util.Calendar; -import java.util.Date; - -import org.apache.commons.lang3.time.DateUtils; - -public enum TemporalPrecisionEnum { - - YEAR(Calendar.YEAR) { - @Override - public Date add(Date theInput, int theAmount) { - return DateUtils.addYears(theInput, theAmount); - } - }, - - MONTH(Calendar.MONTH) { - @Override - public Date add(Date theInput, int theAmount) { - return DateUtils.addMonths(theInput, theAmount); - } - }, - DAY(Calendar.DATE) { - @Override - public Date add(Date theInput, int theAmount) { - return DateUtils.addDays(theInput, theAmount); - } - }, - MINUTE(Calendar.MINUTE) { - @Override - public Date add(Date theInput, int theAmount) { - return DateUtils.addMinutes(theInput, theAmount); - } - }, - SECOND(Calendar.SECOND) { - @Override - public Date add(Date theInput, int theAmount) { - return DateUtils.addSeconds(theInput, theAmount); - } - }, - MILLI(Calendar.MILLISECOND) { - @Override - public Date add(Date theInput, int theAmount) { - return DateUtils.addMilliseconds(theInput, theAmount); - } - }, - - ; - - private int myCalendarConstant; - - TemporalPrecisionEnum(int theCalendarConstant) { - myCalendarConstant = theCalendarConstant; - } - - public abstract Date add(Date theInput, int theAmount); - - public int getCalendarConstant() { - return myCalendarConstant; - } - -} diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/utils/FHIRPathEngine.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/utils/FHIRPathEngine.java index 983ec8657cf..5cbb6218d55 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/utils/FHIRPathEngine.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/utils/FHIRPathEngine.java @@ -1,49 +1,22 @@ package org.hl7.fhir.dstu3.utils; import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Date; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import org.hl7.fhir.dstu3.context.IWorkerContext; -import org.hl7.fhir.dstu3.model.Base; -import org.hl7.fhir.dstu3.model.BooleanType; -import org.hl7.fhir.dstu3.model.DateTimeType; -import org.hl7.fhir.dstu3.model.DateType; -import org.hl7.fhir.dstu3.model.DecimalType; -import org.hl7.fhir.dstu3.model.ElementDefinition; +import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent; -import org.hl7.fhir.dstu3.model.ExpressionNode; -import org.hl7.fhir.dstu3.model.ExpressionNode.CollectionStatus; -import org.hl7.fhir.dstu3.model.ExpressionNode.Function; -import org.hl7.fhir.dstu3.model.ExpressionNode.Kind; -import org.hl7.fhir.dstu3.model.ExpressionNode.Operation; -import org.hl7.fhir.dstu3.model.ExpressionNode.SourceLocation; -import org.hl7.fhir.dstu3.model.IntegerType; -import org.hl7.fhir.dstu3.model.Property; -import org.hl7.fhir.dstu3.model.Resource; -import org.hl7.fhir.dstu3.model.StringType; -import org.hl7.fhir.dstu3.model.StructureDefinition; +import org.hl7.fhir.dstu3.model.ExpressionNode.*; import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionKind; import org.hl7.fhir.dstu3.model.StructureDefinition.TypeDerivationRule; -import org.hl7.fhir.dstu3.model.TemporalPrecisionEnum; -import org.hl7.fhir.dstu3.model.TimeType; -import org.hl7.fhir.dstu3.model.TypeDetails; import org.hl7.fhir.dstu3.model.TypeDetails.ProfiledType; import org.hl7.fhir.dstu3.utils.FHIRLexer.FHIRLexerException; import org.hl7.fhir.dstu3.utils.FHIRPathEngine.IEvaluationContext.FunctionDetails; -import org.hl7.fhir.exceptions.DefinitionException; -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.exceptions.PathEngineException; -import org.hl7.fhir.exceptions.UcumException; +import org.hl7.fhir.exceptions.*; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.ucum.Decimal; +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.ElementUtil; diff --git a/hapi-fhir-structures-dstu3/src/test/java/org/hl7/fhir/dstu3/model/BaseDateTimeTypeDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/org/hl7/fhir/dstu3/model/BaseDateTimeTypeDstu3Test.java index 1ccaf67954c..4f7ec16502b 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/org/hl7/fhir/dstu3/model/BaseDateTimeTypeDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/org/hl7/fhir/dstu3/model/BaseDateTimeTypeDstu3Test.java @@ -3,29 +3,26 @@ package org.hl7.fhir.dstu3.model; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.either; import static org.hamcrest.Matchers.endsWith; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.text.SimpleDateFormat; import java.time.LocalDateTime; import java.time.ZoneOffset; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.Locale; -import java.util.TimeZone; +import java.util.*; import org.apache.commons.lang3.time.FastDateFormat; import org.hamcrest.Matchers; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.*; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.validation.ValidationResult; @@ -65,6 +62,29 @@ public class BaseDateTimeTypeDstu3Test { assertFalse(new DateTimeType("2011-01-01T12:12:12Z").before(new DateTimeType("2011-01-01T12:12:12Z"))); } + @Test + public void testParseMinuteShouldFail() throws DataFormatException { + DateTimeType dt = new DateTimeType(); + try { + dt.setValueAsString("2013-02-03T11:22"); + fail(); + } catch (DataFormatException e) { + assertEquals(e.getMessage(), "Invalid date/time string (datatype DateTimeType does not support MINUTE precision): 2013-02-03T11:22"); + } + } + + @Test + public void testParseMinuteZuluShouldFail() throws DataFormatException { + DateTimeType dt = new DateTimeType(); + try { + dt.setValueAsString("2013-02-03T11:22Z"); + fail(); + } catch (DataFormatException e) { + assertEquals(e.getMessage(), "Invalid date/time string (datatype DateTimeType does not support MINUTE precision): 2013-02-03T11:22Z"); + } + } + + @Test() public void testAfterNull() { try { @@ -112,19 +132,18 @@ public class BaseDateTimeTypeDstu3Test { /** * Test for #57 */ - @SuppressWarnings("unused") @Test public void testConstructorRejectsInvalidPrecision() { try { new DateType("2001-01-02T11:13:33"); fail(); - } catch (IllegalArgumentException e) { + } catch (DataFormatException e) { assertThat(e.getMessage(), containsString("precision")); } try { new InstantType("2001-01-02"); fail(); - } catch (IllegalArgumentException e) { + } catch (DataFormatException e) { assertThat(e.getMessage(), containsString("precision")); } } @@ -335,7 +354,7 @@ public class BaseDateTimeTypeDstu3Test { cal.set(1990, Calendar.JANUARY, 3, 3, 22, 11); DateTimeType date = new DateTimeType(); - date.setValue(cal.getTime(), TemporalPrecisionEnum.MINUTE); + date.setValue(cal.getTime(), ca.uhn.fhir.model.api.TemporalPrecisionEnum.MINUTE); date.setTimeZone(TimeZone.getTimeZone("EST")); assertEquals("1990-01-02T21:22-05:00", date.getValueAsString()); @@ -458,7 +477,7 @@ public class BaseDateTimeTypeDstu3Test { dt.setValueAsString("1974-12-25+10:00"); fail(); } catch (ca.uhn.fhir.parser.DataFormatException e) { - assertEquals("Invalid date/time format: \"1974-12-25+10:00\"", e.getMessage()); + assertEquals("Invalid date/time format: \"1974-12-25+10:00\": Expected character 'T' at index 10 but found +", e.getMessage()); } try { DateTimeType dt = new DateTimeType(); @@ -540,6 +559,26 @@ public class BaseDateTimeTypeDstu3Test { dt.setValueAsString("201302"); } + @Test + public void testParseMinute() throws DataFormatException { + DateTimeType dt = new DateTimeType(); + try { + dt.setValueAsString("2013-02-03T11:22"); + } catch (DataFormatException e) { + assertEquals("Invalid date/time string (datatype DateTimeType does not support MINUTE precision): 2013-02-03T11:22", e.getMessage()); + } + } + + @Test + public void testParseMinuteZulu() throws DataFormatException { + DateTimeType dt = new DateTimeType(); + try { + dt.setValueAsString("2013-02-03T11:22Z"); + } catch (Exception e) { + assertEquals("Invalid date/time string (datatype DateTimeType does not support MINUTE precision): 2013-02-03T11:22Z", e.getMessage()); + } + } + @Test public void testParseSecond() throws DataFormatException { DateTimeType dt = new DateTimeType(); @@ -553,7 +592,7 @@ public class BaseDateTimeTypeDstu3Test { } @Test - public void testParseSecondulu() throws DataFormatException { + public void testParseSecondZulu() throws DataFormatException { DateTimeType dt = new DateTimeType(); dt.setValueAsString("2013-02-03T11:22:33Z"); @@ -721,7 +760,7 @@ public class BaseDateTimeTypeDstu3Test { Date time = cal.getTime(); DateType date = new DateType(); - date.setValue(time, TemporalPrecisionEnum.DAY); + date.setValue(time, ca.uhn.fhir.model.api.TemporalPrecisionEnum.DAY); assertEquals("2012-01-02", date.getValueAsString()); } diff --git a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/model/BaseDateTimeType.java b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/model/BaseDateTimeType.java index 0111c56a286..61ff8733f4c 100644 --- a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/model/BaseDateTimeType.java +++ b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/model/BaseDateTimeType.java @@ -343,7 +343,7 @@ public abstract class BaseDateTimeType extends PrimitiveType { retVal = ourYearMonthDayTimeMilliFormat.parse(theValue); } } catch (ParseException p2) { - throw new IllegalArgumentException("Invalid data/time string (" + p2.getMessage() + "): " + theValue); + throw new IllegalArgumentException("Invalid date/time string (" + p2.getMessage() + "): " + theValue); } setTimeZone(theValue, hasMillis); setPrecision(TemporalPrecisionEnum.MILLI); @@ -357,7 +357,7 @@ public abstract class BaseDateTimeType extends PrimitiveType { retVal = ourYearMonthDayTimeFormat.parse(theValue); } } catch (ParseException p2) { - throw new IllegalArgumentException("Invalid data/time string (" + p2.getMessage() + "): " + theValue); + throw new IllegalArgumentException("Invalid date/time string (" + p2.getMessage() + "): " + theValue); } setTimeZone(theValue, hasMillis); @@ -372,7 +372,7 @@ public abstract class BaseDateTimeType extends PrimitiveType { retVal = ourYearMonthDayTimeMinsFormat.parse(theValue); } } catch (ParseException p2) { - throw new IllegalArgumentException("Invalid data/time string (" + p2.getMessage() + "): " + theValue, p2); + throw new IllegalArgumentException("Invalid date/time string (" + p2.getMessage() + "): " + theValue, p2); } setTimeZone(theValue, hasMillis); diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 8c12874f002..8cb73b1159d 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -24,6 +24,14 @@ looked like before the update. This change was made to support the change above, but seems like a useful feature all around. + + Allow DateParam (used in servers) to handle values with MINUTE precision. Thanks to + Christian Ohr for the pull request! + + + Fix HTTP 500 error in JPA server if a numeric search parameter was supplied with no value, e.g. + GET /Observation?value-quantity=]]> + Optimize queries in JPA server remove a few redundant select columns when performing searches. This provides a slight speed increase in some cases. @@ -202,11 +210,6 @@ was preventing the CLI from uploading definitions correctly. Thanks to Joel Schneider for the Pull Request! - - DateTime datatypes (both DSTU1/2 and RI STU3) did not properly parse - values with MINUTE precision. Thanks to Christian Ohr for the pull - request! - Improve handling in JPA server when doing code:above and code:below searches to use a disjunction of AND and IN in order to avoid failures