Merge branch 'master' of github.com:jamesagnew/hapi-fhir

This commit is contained in:
James 2017-06-16 10:00:27 -04:00
commit b474a1f2ef
26 changed files with 783 additions and 654 deletions

View File

@ -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);
}
}

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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,24 +302,18 @@ 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);
public class DateParamDateTimeHolder extends BaseDateTimeDt {
@Override
protected TemporalPrecisionEnum getDefaultPrecisionForDatatype() {
return TemporalPrecisionEnum.SECOND;
}
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);
@Override
protected boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision) {
return true;
}
}
@Override
public List<DateParam> getValuesAsQueryTokens() {
return Collections.singletonList(this);
}
}

View File

@ -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>

View File

@ -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 });
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 resources.size();
return count;
}
});
}

View File

@ -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

View File

@ -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() {

View File

@ -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);
}

View File

@ -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()

View File

@ -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));

View File

@ -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;

View File

@ -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");

View File

@ -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,15 +23,15 @@ 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
@ -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());
}
}

View File

@ -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);

View File

@ -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;
/**

View File

@ -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;
/**

View File

@ -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;
/**

View File

@ -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.
*/

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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());
}

View File

@ -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);

View File

@ -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>

View File

@ -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