Add date ordinals to subscription matching checker

This commit is contained in:
Gary Graham 2020-02-26 17:05:23 -05:00
parent 2dc94de6bb
commit f86a4c9fa1
14 changed files with 91 additions and 61 deletions

View File

@ -100,10 +100,6 @@ public class DaoConfig {
*/ */
private boolean myAllowInlineMatchUrlReferences = true; private boolean myAllowInlineMatchUrlReferences = true;
private boolean myAllowMultipleDelete; private boolean myAllowMultipleDelete;
/**
* Update setter javadoc if default changes.
*/
private boolean myUseOrdinalDatesForDayPrecisionSearches = true;
/** /**
* update setter javadoc if default changes * update setter javadoc if default changes
*/ */
@ -1912,43 +1908,7 @@ public class DaoConfig {
setPreExpandValueSetsDefaultCount(Math.min(getPreExpandValueSetsDefaultCount(), getPreExpandValueSetsMaxCount())); setPreExpandValueSetsDefaultCount(Math.min(getPreExpandValueSetsDefaultCount(), getPreExpandValueSetsMaxCount()));
} }
/**
* <p>
* Should searches use the integer field {@code SP_VALUE_LOW_DATE_ORDINAL} and {@code SP_VALUE_HIGH_DATE_ORDINAL} in
* {@link ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
* precision of {@link ca.uhn.fhir.model.api.TemporalPrecisionEnum#DAY}.
*
* For example, if enabled, the search of {@code Observation?date=2020-02-25} will cause the date to be collapsed down to an
* ordinal {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
* and {@link ResourceIndexedSearchParamDate#getValueHighDateOrdinal()}
* </p>
* Default is {@literal true} beginning in HAPI FHIR 4.3.
* </p>
*
* @since 4.3
*/
public void setUseOrdinalDatesForDayPrecisionSearches(boolean theUseOrdinalDates) {
myUseOrdinalDatesForDayPrecisionSearches = theUseOrdinalDates;
}
/**
* <p>
* Should searches use the integer field {@code SP_VALUE_LOW_DATE_ORDINAL} and {@code SP_VALUE_HIGH_DATE_ORDINAL} in
* {@link ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
* precision of {@link ca.uhn.fhir.model.api.TemporalPrecisionEnum#DAY}.
*
* For example, if enabled, the search of {@code Observation?date=2020-02-25} will cause the date to be collapsed down to an
* integer representing the ordinal date {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
* and {@link ResourceIndexedSearchParamDate#getValueHighDateOrdinal()}
* </p>
* Default is {@literal true} beginning in HAPI FHIR 4.3.
* </p>
*
* @since 4.3
*/
public boolean getUseOrdinalDatesForDayPrecisionSearches() {
return myUseOrdinalDatesForDayPrecisionSearches;
}
public enum StoreMetaSourceInformationEnum { public enum StoreMetaSourceInformationEnum {
NONE(false, false), NONE(false, false),

View File

@ -50,6 +50,7 @@ abstract class BasePredicateBuilder {
@Autowired @Autowired
DaoConfig myDaoConfig; DaoConfig myDaoConfig;
boolean myDontUseHashesForSearch; boolean myDontUseHashesForSearch;
final IDao myCallingDao; final IDao myCallingDao;
final CriteriaBuilder myBuilder; final CriteriaBuilder myBuilder;

View File

@ -171,7 +171,7 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
* If all present search parameters are of DAY precision, and {@link DaoConfig#getUseOrdinalDatesForDayPrecisionSearches()} is true, * If all present search parameters are of DAY precision, and {@link DaoConfig#getUseOrdinalDatesForDayPrecisionSearches()} is true,
* then we attempt to use the ordinal field for date comparisons instead of the date field. * then we attempt to use the ordinal field for date comparisons instead of the date field.
*/ */
boolean isOrdinalComparison = isNullOrDayPrecision(lowerBound) && isNullOrDayPrecision(upperBound) && myDaoConfig.getUseOrdinalDatesForDayPrecisionSearches(); boolean isOrdinalComparison = isNullOrDayPrecision(lowerBound) && isNullOrDayPrecision(upperBound) && myDaoConfig.getModelConfig().getUseOrdinalDatesForDayPrecisionSearches();
Predicate lt = null; Predicate lt = null;
Predicate gt = null; Predicate gt = null;

View File

@ -884,10 +884,10 @@ public class InMemorySubscriptionMatcherR4Test {
public void testDateSearchParametersShouldBeTimezoneIndependent() { public void testDateSearchParametersShouldBeTimezoneIndependent() {
List<Observation> nlist = new ArrayList<>(); List<Observation> nlist = new ArrayList<>();
nlist.add(createObservationWithEffective("NO1", "2011-01-02T23:00:00-11:30"));
nlist.add(createObservationWithEffective("NO2", "2011-01-03T00:00:00+01:00")); nlist.add(createObservationWithEffective("NO2", "2011-01-03T00:00:00+01:00"));
List<Observation> ylist = new ArrayList<>(); List<Observation> ylist = new ArrayList<>();
nlist.add(createObservationWithEffective("YES00", "2011-01-02T23:00:00-11:30"));
ylist.add(createObservationWithEffective("YES01", "2011-01-02T00:00:00-11:30")); ylist.add(createObservationWithEffective("YES01", "2011-01-02T00:00:00-11:30"));
ylist.add(createObservationWithEffective("YES02", "2011-01-02T00:00:00-10:00")); ylist.add(createObservationWithEffective("YES02", "2011-01-02T00:00:00-10:00"));
ylist.add(createObservationWithEffective("YES03", "2011-01-02T00:00:00-09:00")); ylist.add(createObservationWithEffective("YES03", "2011-01-02T00:00:00-09:00"));

View File

@ -133,7 +133,7 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
public abstract IQueryParameterType toQueryParameterType(); public abstract IQueryParameterType toQueryParameterType();
public boolean matches(IQueryParameterType theParam) { public boolean matches(IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) {
throw new UnsupportedOperationException("No parameter matcher for " + theParam); throw new UnsupportedOperationException("No parameter matcher for " + theParam);
} }

View File

@ -60,6 +60,10 @@ public class ModelConfig {
private String myEmailFromAddress = "noreply@unknown.com"; private String myEmailFromAddress = "noreply@unknown.com";
private boolean mySubscriptionMatchingEnabled = true; private boolean mySubscriptionMatchingEnabled = true;
private String myWebsocketContextPath = DEFAULT_WEBSOCKET_CONTEXT_PATH; private String myWebsocketContextPath = DEFAULT_WEBSOCKET_CONTEXT_PATH;
/**
* Update setter javadoc if default changes.
*/
private boolean myUseOrdinalDatesForDayPrecisionSearches = true;
/** /**
* Constructor * Constructor
@ -257,7 +261,6 @@ public class ModelConfig {
myTreatReferencesAsLogical = new HashSet<>(); myTreatReferencesAsLogical = new HashSet<>();
} }
myTreatReferencesAsLogical.add(theTreatReferencesAsLogical); myTreatReferencesAsLogical.add(theTreatReferencesAsLogical);
} }
/** /**
@ -340,13 +343,12 @@ public class ModelConfig {
return mySubscriptionMatchingEnabled; return mySubscriptionMatchingEnabled;
} }
/** /**
* If set to <code>true</code> (default is true) the server will match incoming resources against active subscriptions * If set to <code>true</code> (default is true) the server will match incoming resources against active subscriptions
* and send them to the subscription channel. If set to <code>false</code> no matching or sending occurs. * and send them to the subscription channel. If set to <code>false</code> no matching or sending occurs.
* @since 3.7.0 * @since 3.7.0
*/ */
public void setSubscriptionMatchingEnabled(boolean theSubscriptionMatchingEnabled) { public void setSubscriptionMatchingEnabled(boolean theSubscriptionMatchingEnabled) {
mySubscriptionMatchingEnabled = theSubscriptionMatchingEnabled; mySubscriptionMatchingEnabled = theSubscriptionMatchingEnabled;
} }
@ -388,6 +390,43 @@ public class ModelConfig {
myWebsocketContextPath = theWebsocketContextPath; myWebsocketContextPath = theWebsocketContextPath;
} }
/**
* <p>
* Should searches use the integer field {@code SP_VALUE_LOW_DATE_ORDINAL} and {@code SP_VALUE_HIGH_DATE_ORDINAL} in
* {@link ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
* precision of {@link ca.uhn.fhir.model.api.TemporalPrecisionEnum#DAY}.
*
* For example, if enabled, the search of {@code Observation?date=2020-02-25} will cause the date to be collapsed down to an
* ordinal {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
* and {@link ResourceIndexedSearchParamDate#getValueHighDateOrdinal()}
* </p>
* Default is {@literal true} beginning in HAPI FHIR 4.3.
* </p>
*
* @since 4.3
*/
public void setUseOrdinalDatesForDayPrecisionSearches(boolean theUseOrdinalDates) {
myUseOrdinalDatesForDayPrecisionSearches = theUseOrdinalDates;
}
/**
* <p>
* Should searches use the integer field {@code SP_VALUE_LOW_DATE_ORDINAL} and {@code SP_VALUE_HIGH_DATE_ORDINAL} in
* {@link ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate} when resolving searches where all predicates are using
* precision of {@link ca.uhn.fhir.model.api.TemporalPrecisionEnum#DAY}.
*
* For example, if enabled, the search of {@code Observation?date=2020-02-25} will cause the date to be collapsed down to an
* integer representing the ordinal date {@code 20200225}. It would then be compared against {@link ResourceIndexedSearchParamDate#getValueLowDateOrdinal()}
* and {@link ResourceIndexedSearchParamDate#getValueHighDateOrdinal()}
* </p>
* Default is {@literal true} beginning in HAPI FHIR 4.3.
* </p>
*
* @since 4.3
*/
public boolean getUseOrdinalDatesForDayPrecisionSearches() {
return myUseOrdinalDatesForDayPrecisionSearches;
}
private static void validateTreatBaseUrlsAsLocal(String theUrl) { private static void validateTreatBaseUrlsAsLocal(String theUrl) {
Validate.notBlank(theUrl, "Base URL must not be null or empty"); Validate.notBlank(theUrl, "Base URL must not be null or empty");

View File

@ -35,7 +35,6 @@ import org.hibernate.search.annotations.Field;
import org.hl7.fhir.r4.model.DateTimeType; import org.hl7.fhir.r4.model.DateTimeType;
import javax.persistence.*; import javax.persistence.*;
import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
@Embeddable @Embeddable
@ -238,21 +237,33 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
} }
@Override @Override
public boolean matches(IQueryParameterType theParam) { public boolean matches(IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) {
if (!(theParam instanceof DateParam)) { if (!(theParam instanceof DateParam)) {
return false; return false;
} }
DateParam dateParam = (DateParam) theParam; DateParam dateParam = (DateParam) theParam;
DateRangeParam range = new DateRangeParam(dateParam); DateRangeParam range = new DateRangeParam(dateParam);
boolean result;
if (theUseOrdinalDatesForDayComparison) {
result = matchesOrdinalDateBounds(range);
} else {
result = matchesDateBounds(range);
}
return result;
}
private boolean matchesDateBounds(DateRangeParam range) {
Date lowerBound = range.getLowerBoundAsInstant(); Date lowerBound = range.getLowerBoundAsInstant();
Date upperBound = range.getUpperBoundAsInstant(); Date upperBound = range.getUpperBoundAsInstant();
if (lowerBound == null && upperBound == null) { if (lowerBound == null && upperBound == null) {
// should never happen // should never happen
return false; return false;
} }
boolean result = true; boolean result = true;
if (lowerBound != null) { if (lowerBound != null) {
result &= (myValueLow.after(lowerBound) || myValueLow.equals(lowerBound)); result &= (myValueLow.after(lowerBound) || myValueLow.equals(lowerBound));
@ -265,8 +276,27 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
return result; return result;
} }
private boolean matchesOrdinalDateBounds(DateRangeParam range) {
boolean result = true;
Integer lowerBoundAsDateInteger = range.getLowerBoundAsDateInteger();
Integer upperBoundAsDateInteger = range.getUpperBoundAsDateInteger();
if (upperBoundAsDateInteger == null && lowerBoundAsDateInteger == null) {
return false;
}
if (lowerBoundAsDateInteger != null) {
result &= (myValueLowDateOrdinal.equals(lowerBoundAsDateInteger) || myValueLowDateOrdinal > lowerBoundAsDateInteger);
result &= (myValueHighDateOrdinal.equals(lowerBoundAsDateInteger) || myValueHighDateOrdinal > lowerBoundAsDateInteger);
}
if (upperBoundAsDateInteger != null) {
result &= (myValueHighDateOrdinal.equals(upperBoundAsDateInteger) || myValueHighDateOrdinal < upperBoundAsDateInteger);
result &= (myValueLowDateOrdinal.equals(upperBoundAsDateInteger) || myValueLowDateOrdinal < upperBoundAsDateInteger);
}
return result;
}
public static Long calculateOrdinalValue(Date theDate) { public static Long calculateOrdinalValue(Date theDate) {
return (long) DateUtils.convertDatetoDayInteger(theDate); return (long) DateUtils.convertDatetoDayInteger(theDate);
}; }
} }

View File

@ -158,7 +158,7 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
} }
@Override @Override
public boolean matches(IQueryParameterType theParam) { public boolean matches(IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) {
if (!(theParam instanceof NumberParam)) { if (!(theParam instanceof NumberParam)) {
return false; return false;
} }

View File

@ -237,7 +237,7 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
} }
@Override @Override
public boolean matches(IQueryParameterType theParam) { public boolean matches(IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) {
if (!(theParam instanceof QuantityParam)) { if (!(theParam instanceof QuantityParam)) {
return false; return false;
} }

View File

@ -314,7 +314,7 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
} }
@Override @Override
public boolean matches(IQueryParameterType theParam) { public boolean matches(IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) {
if (!(theParam instanceof StringParam)) { if (!(theParam instanceof StringParam)) {
return false; return false;
} }

View File

@ -239,7 +239,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
} }
@Override @Override
public boolean matches(IQueryParameterType theParam) { public boolean matches(IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) {
if (!(theParam instanceof TokenParam)) { if (!(theParam instanceof TokenParam)) {
return false; return false;
} }

View File

@ -188,7 +188,7 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
} }
@Override @Override
public boolean matches(IQueryParameterType theParam) { public boolean matches(IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) {
if (!(theParam instanceof UriParam)) { if (!(theParam instanceof UriParam)) {
return false; return false;
} }

View File

@ -217,7 +217,7 @@ public final class ResourceIndexedSearchParams {
return myPopulatedResourceLinkParameters; return myPopulatedResourceLinkParameters;
} }
public boolean matchParam(String theResourceName, String theParamName, RuntimeSearchParam theParamDef, IQueryParameterType theParam) { public boolean matchParam(String theResourceName, String theParamName, RuntimeSearchParam theParamDef, IQueryParameterType theParam, boolean theUseOrdinalDatesForDayComparison) {
if (theParamDef == null) { if (theParamDef == null) {
return false; return false;
} }
@ -254,7 +254,7 @@ public final class ResourceIndexedSearchParams {
} }
Predicate<BaseResourceIndexedSearchParam> namedParamPredicate = param -> Predicate<BaseResourceIndexedSearchParam> namedParamPredicate = param ->
param.getParamName().equalsIgnoreCase(theParamName) && param.getParamName().equalsIgnoreCase(theParamName) &&
param.matches(theParam); param.matches(theParam, theUseOrdinalDatesForDayComparison);
return resourceParams.stream().anyMatch(namedParamPredicate); return resourceParams.stream().anyMatch(namedParamPredicate);
} }

View File

@ -202,7 +202,7 @@ public class InMemoryResourceMatcher {
if (theSearchParams == null) { if (theSearchParams == null) {
return InMemoryMatchResult.successfulMatch(); return InMemoryMatchResult.successfulMatch();
} else { } else {
return InMemoryMatchResult.fromBoolean(theAndOrParams.stream().anyMatch(nextAnd -> matchParams(theResourceName, theParamName, theParamDef, nextAnd, theSearchParams))); return InMemoryMatchResult.fromBoolean(theAndOrParams.stream().anyMatch(nextAnd -> matchParams(theResourceName, theParamName, theParamDef, nextAnd, theSearchParams, myModelConfig.getUseOrdinalDatesForDayPrecisionSearches())));
} }
case COMPOSITE: case COMPOSITE:
case HAS: case HAS:
@ -219,11 +219,11 @@ public class InMemoryResourceMatcher {
} }
} }
private boolean matchParams(String theResourceName, String theParamName, RuntimeSearchParam paramDef, List<? extends IQueryParameterType> theNextAnd, ResourceIndexedSearchParams theSearchParams) { private boolean matchParams(String theResourceName, String theParamName, RuntimeSearchParam paramDef, List<? extends IQueryParameterType> theNextAnd, ResourceIndexedSearchParams theSearchParams, boolean theUseOrdinalDatesForDayComparison) {
if (paramDef.getParamType() == RestSearchParameterTypeEnum.REFERENCE) { if (paramDef.getParamType() == RestSearchParameterTypeEnum.REFERENCE) {
stripBaseUrlsFromReferenceParams(theNextAnd); stripBaseUrlsFromReferenceParams(theNextAnd);
} }
return theNextAnd.stream().anyMatch(token -> theSearchParams.matchParam(theResourceName, theParamName, paramDef, token)); return theNextAnd.stream().anyMatch(token -> theSearchParams.matchParam(theResourceName, theParamName, paramDef, token, theUseOrdinalDatesForDayComparison));
} }
private void stripBaseUrlsFromReferenceParams(List<? extends IQueryParameterType> theNextAnd) { private void stripBaseUrlsFromReferenceParams(List<? extends IQueryParameterType> theNextAnd) {