Merge branch 'master' of github.com:jamesagnew/hapi-fhir
This commit is contained in:
commit
b474a1f2ef
|
@ -84,9 +84,7 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
|
|||
*/
|
||||
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<Date> {
|
|||
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.
|
||||
* <p>
|
||||
* Note that this method returns the
|
||||
* same value as {@link #getNanos()} but with less precision.
|
||||
* </p>
|
||||
*/
|
||||
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
|
||||
* <p>
|
||||
* Note that this method returns the
|
||||
* same value as {@link #getMillis()} but with more precision.
|
||||
* </p>
|
||||
*/
|
||||
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<Date> {
|
|||
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 <code>null</code> if no timezone was
|
||||
* supplied.
|
||||
|
@ -217,10 +285,17 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
|
|||
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<Date> {
|
|||
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<Date> {
|
|||
myFractionalSeconds = "";
|
||||
}
|
||||
|
||||
if (precision == TemporalPrecisionEnum.MINUTE) {
|
||||
validatePrecisionAndThrowDataFormatException(value, precision);
|
||||
}
|
||||
|
||||
setPrecision(precision);
|
||||
return cal.getTime();
|
||||
|
||||
|
@ -372,6 +451,92 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
|
|||
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.
|
||||
* <p>
|
||||
* Note that this method sets the
|
||||
* same value as {@link #setNanos(long)} but with less precision.
|
||||
* </p>
|
||||
*/
|
||||
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
|
||||
* <p>
|
||||
* Note that this method sets the
|
||||
* same value as {@link #setMillis(int)} but with more precision.
|
||||
* </p>
|
||||
*/
|
||||
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<Date> {
|
|||
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<Date> {
|
|||
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<Date> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* <p>
|
||||
* Note that this method returns the
|
||||
* same value as {@link #getNanos()} but with less precision.
|
||||
* </p>
|
||||
*/
|
||||
public Integer getMillis() {
|
||||
return getFieldValue(Calendar.MILLISECOND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nanoseconds within the current second
|
||||
* <p>
|
||||
* Note that this method returns the
|
||||
* same value as {@link #getMillis()} but with more precision.
|
||||
* </p>
|
||||
*/
|
||||
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.
|
||||
* <p>
|
||||
* Note that this method sets the
|
||||
* same value as {@link #setNanos(long)} but with less precision.
|
||||
* </p>
|
||||
*/
|
||||
public BaseDateTimeDt setMillis(int theMillis) {
|
||||
setFieldValue(Calendar.MILLISECOND, theMillis, null, 0, 999);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the nanoseconds within the current second
|
||||
* <p>
|
||||
* Note that this method sets the
|
||||
* same value as {@link #setMillis(int)} but with more precision.
|
||||
* </p>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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<DateParam> implements /*IQueryParameterType , */IQueryParameterOr<DateParam> {
|
||||
|
||||
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<DateParam> implements /*IQuer
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DateParam> getValuesAsQueryTokens() {
|
||||
return Collections.singletonList(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> 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<DateParam> 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<DateParam> 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<DateParam> getValuesAsQueryTokens() {
|
||||
return Collections.singletonList(this);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -100,6 +100,11 @@
|
|||
<artifactId>thymeleaf</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.ttddyy</groupId>
|
||||
<artifactId>datasource-proxy</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.javassist</groupId>
|
||||
|
|
|
@ -33,6 +33,7 @@ import javax.persistence.criteria.Root;
|
|||
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
|
@ -40,8 +41,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.ITermConceptDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.*;
|
||||
import ca.uhn.fhir.jpa.entity.ForcedId;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.util.ReindexFailureException;
|
||||
|
@ -87,30 +87,33 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private IResourceTableDao myResourceTableDao;
|
||||
|
||||
private int doPerformReindexingPassForResources(final Integer theCount, TransactionTemplate txTemplate) {
|
||||
return txTemplate.execute(new TransactionCallback<Integer>() {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Integer doInTransaction(TransactionStatus theStatus) {
|
||||
TypedQuery<ResourceTable> q = myEntityManager.createQuery("SELECT t FROM " + ResourceTable.class.getSimpleName() + " t WHERE t.myIndexStatus IS null", ResourceTable.class);
|
||||
|
||||
int maxResult = 500;
|
||||
if (theCount != null) {
|
||||
maxResult = Math.min(theCount, 2000);
|
||||
}
|
||||
maxResult = Math.max(maxResult, 10);
|
||||
|
||||
TypedQuery<Long> q = myEntityManager.createQuery("SELECT t.myId FROM ResourceTable t WHERE t.myIndexStatus IS NULL", Long.class);
|
||||
|
||||
ourLog.debug("Beginning indexing query with maximum {}", maxResult);
|
||||
q.setMaxResults(maxResult);
|
||||
List<ResourceTable> resources = q.getResultList();
|
||||
if (resources.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ourLog.info("Indexing {} resources", resources.size());
|
||||
Collection<Long> resources = q.getResultList();
|
||||
|
||||
int count = 0;
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
for (ResourceTable resourceTable : resources) {
|
||||
for (Long nextId : resources) {
|
||||
ResourceTable resourceTable = myResourceTableDao.findOne(nextId);
|
||||
|
||||
try {
|
||||
/*
|
||||
* This part is because from HAPI 1.5 - 1.6 we changed the format of forced ID to be "type/id" instead of just "id"
|
||||
|
@ -135,13 +138,22 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
|
|||
throw new ReindexFailureException(resourceTable.getId());
|
||||
}
|
||||
count++;
|
||||
|
||||
if (count >= maxResult) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
long delay = System.currentTimeMillis() - start;
|
||||
long avg = (delay / resources.size());
|
||||
ourLog.info("Indexed {} / {} resources in {}ms - Avg {}ms / resource", new Object[] { count, resources.size(), delay, avg });
|
||||
|
||||
return resources.size();
|
||||
long avg;
|
||||
if (count > 0) {
|
||||
avg = (delay / count);
|
||||
ourLog.info("Indexed {} resources in {}ms - Avg {}ms / resource", new Object[] { count, delay, avg });
|
||||
} else {
|
||||
ourLog.debug("Indexed 0 resources in {}ms", delay);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -64,21 +64,21 @@ public class DaoConfig {
|
|||
*/
|
||||
public static final Long DEFAULT_REUSE_CACHED_SEARCH_RESULTS_FOR_MILLIS = DateUtils.MILLIS_PER_MINUTE;
|
||||
|
||||
// ***
|
||||
// update setter javadoc if default changes
|
||||
// ***
|
||||
/**
|
||||
* update setter javadoc if default changes
|
||||
*/
|
||||
private boolean myAllowExternalReferences = false;
|
||||
|
||||
// ***
|
||||
// update setter javadoc if default changes
|
||||
// ***
|
||||
/**
|
||||
* update setter javadoc if default changes
|
||||
*/
|
||||
private boolean myAllowInlineMatchUrlReferences = true;
|
||||
private boolean myAllowMultipleDelete;
|
||||
private boolean myDefaultSearchParamsCanBeOverridden = false;
|
||||
|
||||
// ***
|
||||
// update setter javadoc if default changes
|
||||
// ***
|
||||
/**
|
||||
* update setter javadoc if default changes
|
||||
*/
|
||||
private int myDeferIndexingForCodesystemsOfSize = 2000;
|
||||
|
||||
private boolean myDeleteStaleSearches = true;
|
||||
|
@ -87,27 +87,34 @@ public class DaoConfig {
|
|||
|
||||
private boolean myEnforceReferentialIntegrityOnWrite = true;
|
||||
|
||||
// ***
|
||||
// update setter javadoc if default changes
|
||||
// ***
|
||||
/**
|
||||
* update setter javadoc if default changes
|
||||
*/
|
||||
private long myExpireSearchResultsAfterMillis = DateUtils.MILLIS_PER_HOUR;
|
||||
private int myHardTagListLimit = 1000;
|
||||
/**
|
||||
* update setter javadoc if default changes
|
||||
*/
|
||||
private Integer myFetchSizeDefaultMaximum = null;
|
||||
|
||||
private int myHardTagListLimit = 1000;
|
||||
private int myIncludeLimit = 2000;
|
||||
// ***
|
||||
// update setter javadoc if default changes
|
||||
// ***
|
||||
/**
|
||||
* update setter javadoc if default changes
|
||||
*/
|
||||
private boolean myIndexContainedResources = true;
|
||||
private List<IServerInterceptor> myInterceptors;
|
||||
// ***
|
||||
// update setter javadoc if default changes
|
||||
// ***
|
||||
/**
|
||||
* update setter javadoc if default changes
|
||||
*/
|
||||
private int myMaximumExpansionSize = 5000;
|
||||
private int myMaximumSearchResultCountInTransaction = DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT_IN_TRANSACTION;
|
||||
private ResourceEncodingEnum myResourceEncoding = ResourceEncodingEnum.JSONC;
|
||||
private Long myReuseCachedSearchResultsForMillis = DEFAULT_REUSE_CACHED_SEARCH_RESULTS_FOR_MILLIS;
|
||||
private boolean mySchedulingDisabled;
|
||||
private boolean mySubscriptionEnabled;
|
||||
/**
|
||||
* update setter javadoc if default changes
|
||||
*/
|
||||
private long mySubscriptionPollDelay = 1000;
|
||||
private Long mySubscriptionPurgeInactiveAfterMillis;
|
||||
private boolean mySuppressUpdatesWithNoChange = true;
|
||||
|
@ -160,6 +167,20 @@ public class DaoConfig {
|
|||
return myExpireSearchResultsAfterMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default maximum number of results to load in a query.
|
||||
* <p>
|
||||
* For example, if the database has a million Patient resources in it, and
|
||||
* the client requests <code>GET /Patient</code>, if this value is set
|
||||
* to a non-null value (default is <code>null</code>) only this number
|
||||
* of results will be fetched. Setting this value appropriately
|
||||
* can be useful to improve performance in some situations.
|
||||
* </p>
|
||||
*/
|
||||
public Integer getFetchSizeDefaultMaximum() {
|
||||
return myFetchSizeDefaultMaximum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum number of results to return in a GetTags query (DSTU1 only)
|
||||
*/
|
||||
|
@ -549,6 +570,20 @@ public class DaoConfig {
|
|||
myExpireSearchResultsAfterMillis = theExpireSearchResultsAfterMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default maximum number of results to load in a query.
|
||||
* <p>
|
||||
* For example, if the database has a million Patient resources in it, and
|
||||
* the client requests <code>GET /Patient</code>, if this value is set
|
||||
* to a non-null value (default is <code>null</code>) only this number
|
||||
* of results will be fetched. Setting this value appropriately
|
||||
* can be useful to improve performance in some situations.
|
||||
* </p>
|
||||
*/
|
||||
public void setFetchSizeDefaultMaximum(Integer theFetchSizeDefaultMaximum) {
|
||||
myFetchSizeDefaultMaximum = theFetchSizeDefaultMaximum;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
|
@ -27,44 +27,21 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.TypedQuery;
|
||||
import javax.persistence.criteria.AbstractQuery;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.*;
|
||||
import javax.persistence.criteria.CriteriaBuilder.In;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Expression;
|
||||
import javax.persistence.criteria.From;
|
||||
import javax.persistence.criteria.Join;
|
||||
import javax.persistence.criteria.JoinType;
|
||||
import javax.persistence.criteria.Order;
|
||||
import javax.persistence.criteria.Path;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
import javax.persistence.criteria.Subquery;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.*;
|
||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.collect.*;
|
||||
|
||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
||||
import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition;
|
||||
|
@ -78,32 +55,12 @@ 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.BaseHasResource;
|
||||
import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamQuantity;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
|
||||
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.jpa.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTag;
|
||||
import ca.uhn.fhir.jpa.entity.SearchParam;
|
||||
import ca.uhn.fhir.jpa.entity.SearchParamPresent;
|
||||
import ca.uhn.fhir.jpa.entity.TagDefinition;
|
||||
import ca.uhn.fhir.jpa.entity.TagTypeEnum;
|
||||
import ca.uhn.fhir.jpa.entity.*;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
|
||||
import ca.uhn.fhir.jpa.term.VersionIndependentConcept;
|
||||
import ca.uhn.fhir.jpa.util.StopWatch;
|
||||
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.api.Include;
|
||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
|
||||
import ca.uhn.fhir.model.base.composite.BaseIdentifierDt;
|
||||
import ca.uhn.fhir.model.base.composite.BaseQuantityDt;
|
||||
import ca.uhn.fhir.model.api.*;
|
||||
import ca.uhn.fhir.model.base.composite.*;
|
||||
import ca.uhn.fhir.model.dstu.resource.BaseResource;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
|
@ -112,23 +69,9 @@ import ca.uhn.fhir.parser.DataFormatException;
|
|||
import ca.uhn.fhir.rest.api.SortOrderEnum;
|
||||
import ca.uhn.fhir.rest.api.SortSpec;
|
||||
import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.param.CompositeParam;
|
||||
import ca.uhn.fhir.rest.param.DateParam;
|
||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
import ca.uhn.fhir.rest.param.HasParam;
|
||||
import ca.uhn.fhir.rest.param.NumberParam;
|
||||
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
||||
import ca.uhn.fhir.rest.param.QuantityParam;
|
||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||
import ca.uhn.fhir.rest.param.StringParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParamModifier;
|
||||
import ca.uhn.fhir.rest.param.UriParam;
|
||||
import ca.uhn.fhir.rest.param.UriParamQualifierEnum;
|
||||
import ca.uhn.fhir.rest.param.*;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.*;
|
||||
import ca.uhn.fhir.util.UrlUtil;
|
||||
|
||||
/**
|
||||
|
@ -138,12 +81,14 @@ import ca.uhn.fhir.util.UrlUtil;
|
|||
public class SearchBuilder implements ISearchBuilder {
|
||||
private static Long NO_MORE = Long.valueOf(-1);
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchBuilder.class);
|
||||
private List<Long> myAlsoIncludePids;
|
||||
private CriteriaBuilder myBuilder;
|
||||
private BaseHapiFhirDao<?> myCallingDao;
|
||||
private FhirContext myContext;
|
||||
private EntityManager myEntityManager;
|
||||
private IForcedIdDao myForcedIdDao;
|
||||
private IFulltextSearchSvc myFulltextSearchSvc;
|
||||
private Map<JoinKey, Join<?, ?>> myIndexJoins = Maps.newHashMap();
|
||||
private SearchParameterMap myParams;
|
||||
private ArrayList<Predicate> myPredicates;
|
||||
private IResourceIndexedSearchParamUriDao myResourceIndexedSearchParamUriDao;
|
||||
|
@ -152,8 +97,8 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
private Root<ResourceTable> myResourceTableRoot;
|
||||
private Class<? extends IBaseResource> myResourceType;
|
||||
private ISearchParamRegistry mySearchParamRegistry;
|
||||
private IHapiTerminologySvc myTerminologySvc;
|
||||
private String mySearchUuid;
|
||||
private IHapiTerminologySvc myTerminologySvc;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -192,7 +137,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
|
||||
private void addPredicateDate(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList) {
|
||||
|
||||
Join<ResourceTable, ResourceIndexedSearchParamDate> join = myResourceTableRoot.join("myParamsDate", JoinType.LEFT);
|
||||
Join<ResourceTable, ResourceIndexedSearchParamDate> join = createOrReuseJoin(JoinEnum.DATE, theParamName);
|
||||
|
||||
if (theList.get(0).getMissing() != null) {
|
||||
Boolean missing = theList.get(0).getMissing();
|
||||
|
@ -268,28 +213,6 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
// private void addPredicateId(Set<Long> thePids) {
|
||||
// if (thePids == null || thePids.isEmpty()) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
|
||||
// CriteriaQuery<Long> cq = builder.createQuery(Long.class);
|
||||
// Root<ResourceTable> from = cq.from(ResourceTable.class);
|
||||
// cq.select(from.get("myId").as(Long.class));
|
||||
//
|
||||
// List<Predicate> predicates = new ArrayList<Predicate>();
|
||||
// predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
|
||||
// predicates.add(from.get("myId").in(thePids));
|
||||
// createPredicateResourceId(builder, cq, predicates, from.get("myId").as(Long.class));
|
||||
// createPredicateLastUpdatedForResourceTable(builder, from, predicates);
|
||||
//
|
||||
// cq.where(toArray(predicates));
|
||||
//
|
||||
// TypedQuery<Long> q = myEntityManager.createQuery(cq);
|
||||
// doSetPids(q.getResultList());
|
||||
// }
|
||||
|
||||
private void addPredicateLanguage(List<List<? extends IQueryParameterType>> theList) {
|
||||
for (List<? extends IQueryParameterType> nextList : theList) {
|
||||
|
||||
|
@ -319,7 +242,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
|
||||
private void addPredicateNumber(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList) {
|
||||
|
||||
Join<ResourceTable, ResourceIndexedSearchParamNumber> join = myResourceTableRoot.join("myParamsNumber", JoinType.LEFT);
|
||||
Join<ResourceTable, ResourceIndexedSearchParamNumber> join = createOrReuseJoin(JoinEnum.NUMBER, theParamName);
|
||||
|
||||
if (theList.get(0).getMissing() != null) {
|
||||
addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
|
||||
|
@ -371,7 +294,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
}
|
||||
|
||||
private void addPredicateQuantity(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList) {
|
||||
Join<ResourceTable, ResourceIndexedSearchParamQuantity> join = myResourceTableRoot.join("myParamsQuantity", JoinType.LEFT);
|
||||
Join<ResourceTable, ResourceIndexedSearchParamQuantity> join = createOrReuseJoin(JoinEnum.QUANTITY, theParamName);
|
||||
|
||||
if (theList.get(0).getMissing() != null) {
|
||||
addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
|
||||
|
@ -399,7 +322,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
return;
|
||||
}
|
||||
|
||||
Join<ResourceTable, ResourceLink> join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT);
|
||||
Join<ResourceTable, ResourceLink> join = createOrReuseJoin(JoinEnum.REFERENCE, theParamName);
|
||||
|
||||
List<Predicate> codePredicates = new ArrayList<Predicate>();
|
||||
|
||||
|
@ -459,7 +382,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(theResourceName);
|
||||
RuntimeSearchParam searchParamByName = myCallingDao.getSearchParamByName(resourceDef, theParamName);
|
||||
if (searchParamByName == null) {
|
||||
throw new InternalErrorException("Could not find parameter " + theParamName );
|
||||
throw new InternalErrorException("Could not find parameter " + theParamName);
|
||||
}
|
||||
String paramPath = searchParamByName.getPath();
|
||||
if (paramPath.endsWith(".as(Reference)")) {
|
||||
|
@ -489,7 +412,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
if (resourceTypes.isEmpty()) {
|
||||
for (BaseRuntimeElementDefinition<?> next : myContext.getElementDefinitions()) {
|
||||
if (next instanceof RuntimeResourceDefinition) {
|
||||
RuntimeResourceDefinition nextResDef = (RuntimeResourceDefinition)next;
|
||||
RuntimeResourceDefinition nextResDef = (RuntimeResourceDefinition) next;
|
||||
resourceTypes.add(nextResDef.getImplementingClass());
|
||||
}
|
||||
}
|
||||
|
@ -575,8 +498,10 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
*/
|
||||
Root<ResourceTable> stackRoot = myResourceTableRoot;
|
||||
ArrayList<Predicate> stackPredicates = myPredicates;
|
||||
Map<JoinKey, Join<?, ?>> stackIndexJoins = myIndexJoins;
|
||||
myResourceTableRoot = subQfrom;
|
||||
myPredicates = new ArrayList<Predicate>();
|
||||
myPredicates = Lists.newArrayList();
|
||||
myIndexJoins = Maps.newHashMap();
|
||||
|
||||
// Create the subquery predicates
|
||||
myPredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), subResourceName));
|
||||
|
@ -590,6 +515,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
*/
|
||||
myResourceTableRoot = stackRoot;
|
||||
myPredicates = stackPredicates;
|
||||
myIndexJoins = stackIndexJoins;
|
||||
|
||||
Predicate pathPredicate = createResourceLinkPathPredicate(theResourceName, theParamName, join);
|
||||
Predicate pidPredicate = join.get("myTargetResourcePid").in(subQ);
|
||||
|
@ -654,7 +580,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
|
||||
private void addPredicateString(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList) {
|
||||
|
||||
Join<ResourceTable, ResourceIndexedSearchParamString> join = myResourceTableRoot.join("myParamsString", JoinType.LEFT);
|
||||
Join<ResourceTable, ResourceIndexedSearchParamString> join = createOrReuseJoin(JoinEnum.STRING, theParamName);
|
||||
|
||||
if (theList.get(0).getMissing() != null) {
|
||||
addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
|
||||
|
@ -802,7 +728,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
|
||||
private void addPredicateToken(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList) {
|
||||
|
||||
Join<ResourceTable, ResourceIndexedSearchParamToken> join = myResourceTableRoot.join("myParamsToken", JoinType.LEFT);
|
||||
Join<ResourceTable, ResourceIndexedSearchParamToken> join = createOrReuseJoin(JoinEnum.TOKEN, theParamName);
|
||||
|
||||
if (theList.get(0).getMissing() != null) {
|
||||
addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
|
||||
|
@ -834,7 +760,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
|
||||
private void addPredicateUri(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList) {
|
||||
|
||||
Join<ResourceTable, ResourceIndexedSearchParamUri> join = myResourceTableRoot.join("myParamsUri", JoinType.LEFT);
|
||||
Join<ResourceTable, ResourceIndexedSearchParamUri> join = createOrReuseJoin(JoinEnum.URI, theParamName);
|
||||
|
||||
if (theList.get(0).getMissing() != null) {
|
||||
addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
|
||||
|
@ -959,6 +885,42 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> Join<ResourceTable, T> createOrReuseJoin(JoinEnum theType, String theSearchParameterName) {
|
||||
Join<ResourceTable, ResourceIndexedSearchParamDate> join = null;
|
||||
|
||||
switch (theType) {
|
||||
case DATE:
|
||||
join = myResourceTableRoot.join("myParamsDate", JoinType.LEFT);
|
||||
break;
|
||||
case NUMBER:
|
||||
join = myResourceTableRoot.join("myParamsNumber", JoinType.LEFT);
|
||||
break;
|
||||
case QUANTITY:
|
||||
join = myResourceTableRoot.join("myParamsQuantity", JoinType.LEFT);
|
||||
break;
|
||||
case REFERENCE:
|
||||
join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT);
|
||||
break;
|
||||
case STRING:
|
||||
join = myResourceTableRoot.join("myParamsString", JoinType.LEFT);
|
||||
break;
|
||||
case URI:
|
||||
join = myResourceTableRoot.join("myParamsUri", JoinType.LEFT);
|
||||
break;
|
||||
case TOKEN:
|
||||
join = myResourceTableRoot.join("myParamsToken", JoinType.LEFT);
|
||||
break;
|
||||
}
|
||||
|
||||
JoinKey key = new JoinKey(theSearchParameterName, theType);
|
||||
if (!myIndexJoins.containsKey(key)) {
|
||||
myIndexJoins.put(key, join);
|
||||
}
|
||||
|
||||
return (Join<ResourceTable, T>) join;
|
||||
}
|
||||
|
||||
private Predicate createPredicateDate(IQueryParameterType theParam, String theResourceName, String theParamName, CriteriaBuilder theBuilder, From<?, ResourceIndexedSearchParamDate> theFrom) {
|
||||
Predicate p;
|
||||
if (theParam instanceof DateParam) {
|
||||
|
@ -1238,10 +1200,10 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
// Use "in" in case of large numbers of codes due to param modifiers
|
||||
final Path<String> systemExpression = theFrom.get("mySystem");
|
||||
final Path<String> valueExpression = theFrom.get("myValue");
|
||||
for (Map.Entry<String, List<VersionIndependentConcept>> entry: map.entrySet()) {
|
||||
for (Map.Entry<String, List<VersionIndependentConcept>> entry : map.entrySet()) {
|
||||
Predicate systemPredicate = theBuilder.equal(systemExpression, entry.getKey());
|
||||
In<String> codePredicate = theBuilder.in(valueExpression);
|
||||
for (VersionIndependentConcept nextCode: entry.getValue()) {
|
||||
for (VersionIndependentConcept nextCode : entry.getValue()) {
|
||||
codePredicate.value(nextCode.getCode());
|
||||
}
|
||||
orPredicates.add(theBuilder.and(systemPredicate, codePredicate));
|
||||
|
@ -1291,8 +1253,6 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
return new QueryIterator();
|
||||
}
|
||||
|
||||
private List<Long> myAlsoIncludePids;
|
||||
|
||||
private TypedQuery<Long> createQuery(SortSpec sort) {
|
||||
CriteriaQuery<Long> outerQuery;
|
||||
/*
|
||||
|
@ -1337,16 +1297,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
|
||||
}
|
||||
|
||||
myResourceTableQuery.distinct(true);
|
||||
myPredicates = new ArrayList<Predicate>();
|
||||
if (myParams.getEverythingMode() == null) {
|
||||
myPredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), myResourceName));
|
||||
}
|
||||
myPredicates.add(myBuilder.isNull(myResourceTableRoot.get("myDeleted")));
|
||||
|
||||
DateRangeParam lu = myParams.getLastUpdated();
|
||||
List<Predicate> lastUpdatedPredicates = createLastUpdatedPredicates(lu, myBuilder, myResourceTableRoot);
|
||||
myPredicates.addAll(lastUpdatedPredicates);
|
||||
|
||||
if (myParams.getEverythingMode() != null) {
|
||||
Join<ResourceTable, ResourceLink> join = myResourceTableRoot.join("myResourceLinks", JoinType.LEFT);
|
||||
|
@ -1390,6 +1341,25 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
myPredicates.add(myResourceTableRoot.get("myId").as(Long.class).in(pids));
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a predicate to make sure we only include non-deleted resources, and only include
|
||||
* resources of the right type.
|
||||
*
|
||||
* If we have any joins to index tables, we get this behaviour already guaranteed so we don't
|
||||
* need an explicit predicate for it.
|
||||
*/
|
||||
if (myIndexJoins.isEmpty()) {
|
||||
if (myParams.getEverythingMode() == null) {
|
||||
myPredicates.add(myBuilder.equal(myResourceTableRoot.get("myResourceType"), myResourceName));
|
||||
}
|
||||
myPredicates.add(myBuilder.isNull(myResourceTableRoot.get("myDeleted")));
|
||||
}
|
||||
|
||||
// Last updated
|
||||
DateRangeParam lu = myParams.getLastUpdated();
|
||||
List<Predicate> lastUpdatedPredicates = createLastUpdatedPredicates(lu, myBuilder, myResourceTableRoot);
|
||||
myPredicates.addAll(lastUpdatedPredicates);
|
||||
|
||||
myResourceTableQuery.where(myBuilder.and(SearchBuilder.toArray(myPredicates)));
|
||||
|
||||
/*
|
||||
|
@ -1443,47 +1413,65 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
|
||||
String joinAttrName;
|
||||
String[] sortAttrName;
|
||||
JoinEnum joinType;
|
||||
|
||||
switch (param.getParamType()) {
|
||||
case STRING:
|
||||
joinAttrName = "myParamsString";
|
||||
sortAttrName = new String[] { "myValueExact" };
|
||||
joinType = JoinEnum.STRING;
|
||||
break;
|
||||
case DATE:
|
||||
joinAttrName = "myParamsDate";
|
||||
sortAttrName = new String[] { "myValueLow" };
|
||||
joinType = JoinEnum.DATE;
|
||||
break;
|
||||
case REFERENCE:
|
||||
joinAttrName = "myResourceLinks";
|
||||
sortAttrName = new String[] { "myTargetResourcePid" };
|
||||
joinType = JoinEnum.REFERENCE;
|
||||
break;
|
||||
case TOKEN:
|
||||
joinAttrName = "myParamsToken";
|
||||
sortAttrName = new String[] { "mySystem", "myValue" };
|
||||
joinType = JoinEnum.TOKEN;
|
||||
break;
|
||||
case NUMBER:
|
||||
joinAttrName = "myParamsNumber";
|
||||
sortAttrName = new String[] { "myValue" };
|
||||
joinType = JoinEnum.NUMBER;
|
||||
break;
|
||||
case URI:
|
||||
joinAttrName = "myParamsUri";
|
||||
sortAttrName = new String[] { "myUri" };
|
||||
joinType = JoinEnum.URI;
|
||||
break;
|
||||
case QUANTITY:
|
||||
joinAttrName = "myParamsQuantity";
|
||||
sortAttrName = new String[] { "myValue" };
|
||||
joinType = JoinEnum.QUANTITY;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidRequestException("This server does not support _sort specifications of type " + param.getParamType() + " - Can't serve _sort=" + theSort.getParamName());
|
||||
}
|
||||
|
||||
From<?, ?> join = theFrom.join(joinAttrName, JoinType.LEFT);
|
||||
/*
|
||||
* If we've already got a join for the specific parameter we're
|
||||
* sorting on, we'll also sort with it. Otherwise we need a new join.
|
||||
*/
|
||||
JoinKey key = new JoinKey(theSort.getParamName(), joinType);
|
||||
Join<?, ?> join = myIndexJoins.get(key);
|
||||
if (join == null) {
|
||||
join = theFrom.join(joinAttrName, JoinType.LEFT);
|
||||
|
||||
if (param.getParamType() == RestSearchParameterTypeEnum.REFERENCE) {
|
||||
thePredicates.add(join.get("mySourcePath").as(String.class).in(param.getPathsSplit()));
|
||||
if (param.getParamType() == RestSearchParameterTypeEnum.REFERENCE) {
|
||||
thePredicates.add(join.get("mySourcePath").as(String.class).in(param.getPathsSplit()));
|
||||
} else {
|
||||
Predicate joinParam1 = theBuilder.equal(join.get("myParamName"), theSort.getParamName());
|
||||
thePredicates.add(joinParam1);
|
||||
}
|
||||
} else {
|
||||
Predicate joinParam1 = theBuilder.equal(join.get("myParamName"), theSort.getParamName());
|
||||
thePredicates.add(joinParam1);
|
||||
ourLog.info("Reusing join for {}", theSort.getParamName());
|
||||
}
|
||||
|
||||
for (String next : sortAttrName) {
|
||||
|
@ -1529,41 +1517,6 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadResourcesByPid(Collection<Long> theIncludePids, List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation,
|
||||
EntityManager entityManager, FhirContext context, IDao theDao) {
|
||||
if (theIncludePids.isEmpty()) {
|
||||
ourLog.info("The include pids are empty");
|
||||
//return;
|
||||
}
|
||||
|
||||
// Dupes will cause a crash later anyhow, but this is expensive so only do it
|
||||
// when running asserts
|
||||
assert new HashSet<Long>(theIncludePids).size() == theIncludePids.size() : "PID list contains duplicates: " + theIncludePids;
|
||||
|
||||
Map<Long, Integer> position = new HashMap<Long, Integer>();
|
||||
for (Long next : theIncludePids) {
|
||||
position.put(next, theResourceListToPopulate.size());
|
||||
theResourceListToPopulate.add(null);
|
||||
}
|
||||
|
||||
/*
|
||||
* As always, Oracle can't handle things that other databases don't mind.. In this
|
||||
* case it doesn't like more than ~1000 IDs in a single load, so we break this up
|
||||
* if it's lots of IDs. I suppose maybe we should be doing this as a join anyhow
|
||||
* but this should work too. Sigh.
|
||||
*/
|
||||
int maxLoad = 800;
|
||||
List<Long> pids = new ArrayList<Long>(theIncludePids);
|
||||
for (int i = 0; i < pids.size(); i += maxLoad) {
|
||||
int to = i + maxLoad;
|
||||
to = Math.min(to, pids.size());
|
||||
List<Long> pidsSubList = pids.subList(i, to);
|
||||
doLoadPids(theResourceListToPopulate, theRevIncludedPids, theForHistoryOperation, entityManager, context, theDao, position, pidsSubList);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void doLoadPids(List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation, EntityManager entityManager, FhirContext context, IDao theDao,
|
||||
Map<Long, Integer> position, Collection<Long> pids) {
|
||||
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
|
||||
|
@ -1599,6 +1552,41 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadResourcesByPid(Collection<Long> theIncludePids, List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation,
|
||||
EntityManager entityManager, FhirContext context, IDao theDao) {
|
||||
if (theIncludePids.isEmpty()) {
|
||||
ourLog.info("The include pids are empty");
|
||||
// return;
|
||||
}
|
||||
|
||||
// Dupes will cause a crash later anyhow, but this is expensive so only do it
|
||||
// when running asserts
|
||||
assert new HashSet<Long>(theIncludePids).size() == theIncludePids.size() : "PID list contains duplicates: " + theIncludePids;
|
||||
|
||||
Map<Long, Integer> position = new HashMap<Long, Integer>();
|
||||
for (Long next : theIncludePids) {
|
||||
position.put(next, theResourceListToPopulate.size());
|
||||
theResourceListToPopulate.add(null);
|
||||
}
|
||||
|
||||
/*
|
||||
* As always, Oracle can't handle things that other databases don't mind.. In this
|
||||
* case it doesn't like more than ~1000 IDs in a single load, so we break this up
|
||||
* if it's lots of IDs. I suppose maybe we should be doing this as a join anyhow
|
||||
* but this should work too. Sigh.
|
||||
*/
|
||||
int maxLoad = 800;
|
||||
List<Long> pids = new ArrayList<Long>(theIncludePids);
|
||||
for (int i = 0; i < pids.size(); i += maxLoad) {
|
||||
int to = i + maxLoad;
|
||||
to = Math.min(to, pids.size());
|
||||
List<Long> pidsSubList = pids.subList(i, to);
|
||||
doLoadPids(theResourceListToPopulate, theRevIncludedPids, theForHistoryOperation, entityManager, context, theDao, position, pidsSubList);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* THIS SHOULD RETURN HASHSET and not jsut Set because we add to it later (so it can't be Collections.emptySet())
|
||||
*
|
||||
|
@ -1870,7 +1858,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setType(Class<? extends IBaseResource> theResourceType, String theResourceName) {
|
||||
public void setType(Class<? extends IBaseResource> theResourceType, String theResourceName) {
|
||||
myResourceType = theResourceType;
|
||||
myResourceName = theResourceName;
|
||||
}
|
||||
|
@ -1989,13 +1977,46 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
return thePredicates.toArray(new Predicate[thePredicates.size()]);
|
||||
}
|
||||
|
||||
private enum JoinEnum {
|
||||
DATE, NUMBER, QUANTITY, REFERENCE, STRING, TOKEN, URI
|
||||
|
||||
}
|
||||
|
||||
private static class JoinKey {
|
||||
private final JoinEnum myJoinType;
|
||||
private final String myParamName;
|
||||
|
||||
public JoinKey(String theParamName, JoinEnum theJoinType) {
|
||||
super();
|
||||
myParamName = theParamName;
|
||||
myJoinType = theJoinType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object theObj) {
|
||||
JoinKey obj = (JoinKey) theObj;
|
||||
return new EqualsBuilder()
|
||||
.append(myParamName, obj.myParamName)
|
||||
.append(myJoinType, obj.myJoinType)
|
||||
.isEquals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return new HashCodeBuilder()
|
||||
.append(myParamName)
|
||||
.append(myJoinType)
|
||||
.toHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
private final class QueryIterator implements Iterator<Long> {
|
||||
private boolean myFirst = true;
|
||||
private Long myNext;
|
||||
private final Set<Long> myPidSet = new HashSet<Long>();
|
||||
private Iterator<Long> myPreResultsIterator;
|
||||
private Iterator<Long> myResultsIterator;
|
||||
private SortSpec mySort;
|
||||
private Iterator<Long> myPreResultsIterator;
|
||||
private boolean myFirst = true;
|
||||
private StopWatch myStopwatch = null;
|
||||
|
||||
private QueryIterator() {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package ca.uhn.fhir.jpa.dao.data;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
|
@ -19,10 +21,7 @@ package ca.uhn.fhir.jpa.dao.data;
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.jpa.repository.*;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
|
@ -33,4 +32,7 @@ public interface IResourceTableDao extends JpaRepository<ResourceTable, Long> {
|
|||
@Query("UPDATE ResourceTable r SET r.myIndexStatus = null WHERE r.myResourceType = :restype")
|
||||
int markResourcesOfTypeAsRequiringReindexing(@Param("restype") String theResourceType);
|
||||
|
||||
@Query("SELECT t.myId FROM ResourceTable t WHERE t.myIndexStatus IS NULL")
|
||||
Slice<Long> findUnindexed(Pageable thePageRequest);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ca.uhn.fhir.jpa.config;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.sql.DataSource;
|
||||
|
@ -17,6 +18,8 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
|||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
|
||||
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
||||
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
|
||||
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
|
||||
|
||||
@Configuration
|
||||
@EnableTransactionManagement()
|
||||
|
@ -34,7 +37,15 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
|
|||
retVal.setUrl("jdbc:derby:memory:myUnitTestDB;create=true");
|
||||
retVal.setUsername("");
|
||||
retVal.setPassword("");
|
||||
return retVal;
|
||||
|
||||
DataSource dataSource = ProxyDataSourceBuilder
|
||||
.create(retVal)
|
||||
.logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL")
|
||||
.logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
|
||||
.countQuery()
|
||||
.build();
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
@Bean()
|
||||
|
|
|
@ -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;
|
||||
|
@ -82,8 +81,7 @@ import org.springframework.transaction.TransactionStatus;
|
|||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.dao.*;
|
||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap.EverythingModeEnum;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber;
|
||||
|
@ -93,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;
|
||||
|
@ -130,6 +129,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
|
|||
@Before
|
||||
public void beforeDisableResultReuse() {
|
||||
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||
myDaoConfig.setFetchSizeDefaultMaximum(new DaoConfig().getFetchSizeDefaultMaximum());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1844,6 +1844,48 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchStringParamDoesntMatchWrongType() throws Exception {
|
||||
IIdType pid1;
|
||||
IIdType pid2;
|
||||
{
|
||||
Patient patient = new Patient();
|
||||
patient.addName().setFamily("HELLO");
|
||||
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
{
|
||||
Practitioner patient = new Practitioner();
|
||||
patient.addName().setFamily("HELLO");
|
||||
pid2 = myPractitionerDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
SearchParameterMap params;
|
||||
List<IIdType> patients;
|
||||
|
||||
params = new SearchParameterMap();
|
||||
params.add(Patient.SP_FAMILY, new StringParam("HELLO"));
|
||||
patients = toUnqualifiedVersionlessIds(myPatientDao.search(params));
|
||||
assertThat(patients, containsInAnyOrder(pid1));
|
||||
assertThat(patients, not(containsInAnyOrder(pid2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchWithFetchSizeDefaultMaximum() {
|
||||
myDaoConfig.setFetchSizeDefaultMaximum(5);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
Patient p = new Patient();
|
||||
p.addName().setFamily("PT" + i);
|
||||
myPatientDao.create(p);
|
||||
}
|
||||
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.setLoadSynchronous(true);
|
||||
IBundleProvider values = myPatientDao.search(map);
|
||||
assertEquals(5, values.size().intValue());
|
||||
assertEquals(5, values.getResources(0, 1000).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchStringParam() throws Exception {
|
||||
IIdType pid1;
|
||||
|
@ -3268,11 +3310,26 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
|
|||
SearchParameterMap map;
|
||||
List<String> ids;
|
||||
|
||||
// No search param
|
||||
map = new SearchParameterMap();
|
||||
map.setSort(new SortSpec("family", SortOrderEnum.ASC).setChain(new SortSpec("given", SortOrderEnum.ASC)));
|
||||
ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
|
||||
assertThat(ids, contains("Patient/AA", "Patient/AB", "Patient/BA", "Patient/BB"));
|
||||
|
||||
// Same SP as sort
|
||||
map = new SearchParameterMap();
|
||||
map.add(Patient.SP_ACTIVE, new TokenParam(null, "true"));
|
||||
map.setSort(new SortSpec("family", SortOrderEnum.ASC).setChain(new SortSpec("given", SortOrderEnum.ASC)));
|
||||
ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
|
||||
assertThat(ids, contains("Patient/AA", "Patient/AB", "Patient/BA", "Patient/BB"));
|
||||
|
||||
// Different SP from sort
|
||||
map = new SearchParameterMap();
|
||||
map.add(Patient.SP_GENDER, new TokenParam(null, "male"));
|
||||
map.setSort(new SortSpec("family", SortOrderEnum.ASC).setChain(new SortSpec("given", SortOrderEnum.ASC)));
|
||||
ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
|
||||
assertThat(ids, contains("Patient/AA", "Patient/AB", "Patient/BA", "Patient/BB"));
|
||||
|
||||
map = new SearchParameterMap();
|
||||
map.setSort(new SortSpec("gender").setChain(new SortSpec("family", SortOrderEnum.ASC).setChain(new SortSpec("given", SortOrderEnum.ASC))));
|
||||
ids = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<Date> {
|
||||
|
@ -44,9 +45,7 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
|
|||
*/
|
||||
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<Date> {
|
|||
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<Date> {
|
|||
*/
|
||||
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<Date> {
|
|||
*/
|
||||
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<Date> {
|
|||
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<Date> {
|
|||
myFractionalSeconds = "";
|
||||
}
|
||||
|
||||
if (precision == TemporalPrecisionEnum.MINUTE) {
|
||||
validatePrecisionAndThrowDataFormatException(value, precision);
|
||||
}
|
||||
|
||||
myPrecision = precision;
|
||||
return cal.getTime();
|
||||
|
||||
|
@ -818,6 +820,12 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -343,7 +343,7 @@ public abstract class BaseDateTimeType extends PrimitiveType<Date> {
|
|||
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<Date> {
|
|||
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<Date> {
|
|||
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);
|
||||
|
|
5
pom.xml
5
pom.xml
|
@ -538,6 +538,11 @@
|
|||
<artifactId>Saxon-HE</artifactId>
|
||||
<version>9.5.1-5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.ttddyy</groupId>
|
||||
<artifactId>datasource-proxy</artifactId>
|
||||
<version>1.4.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-dbcp2</artifactId>
|
||||
|
|
|
@ -24,6 +24,10 @@
|
|||
looked like before the update. This change was made to support the change above, but
|
||||
seems like a useful feature all around.
|
||||
</action>
|
||||
<action type="fix" issue="604">
|
||||
Allow DateParam (used in servers) to handle values with MINUTE precision. Thanks to
|
||||
Christian Ohr for the pull request!
|
||||
</action>
|
||||
<action type="fix">
|
||||
Fix HTTP 500 error in JPA server if a numeric search parameter was supplied with no value, e.g.
|
||||
<![CDATA[<code>GET /Observation?value-quantity=</code>]]>
|
||||
|
@ -33,6 +37,17 @@
|
|||
created and updated resource bodies in the response bundle if it is set
|
||||
appropriately.
|
||||
</action>
|
||||
<action type="add">
|
||||
Optimize queries in JPA server remove a few redundant select columns when performing
|
||||
searches. This provides a slight speed increase in some cases.
|
||||
</action>
|
||||
<action type="add">
|
||||
Add configuration to JPA server DaoConfig that allows a maximum
|
||||
number of search results to be specified. Queries will never return
|
||||
more than this number, which can be good for avoiding accidental
|
||||
performance problems in situations where lare queries should not be
|
||||
needed
|
||||
</action>
|
||||
</release>
|
||||
<release version="2.5" date="2017-06-08">
|
||||
<action type="fix">
|
||||
|
@ -200,11 +215,6 @@
|
|||
was preventing the CLI from uploading definitions correctly. Thanks to
|
||||
Joel Schneider for the Pull Request!
|
||||
</action>
|
||||
<action type="fix" issue="655">
|
||||
DateTime datatypes (both DSTU1/2 and RI STU3) did not properly parse
|
||||
values with MINUTE precision. Thanks to Christian Ohr for the pull
|
||||
request!
|
||||
</action>
|
||||
<action type="add" issue="656">
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue