started to refactor predicates out of SearchBuilder

This commit is contained in:
Ken Stevens 2020-01-23 12:00:12 -05:00
parent e6f5af01ed
commit 4c53333539
16 changed files with 2243 additions and 1804 deletions

View File

@ -317,7 +317,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
} }
@Override @Override
protected DaoConfig getConfig() { public DaoConfig getConfig() {
return myConfig; return myConfig;
} }

View File

@ -0,0 +1,229 @@
package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresent;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import javax.persistence.criteria.*;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.ArrayList;
import java.util.List;
abstract class BasePredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(BasePredicateBuilder.class);
@Autowired
FhirContext myContext;
@Autowired
DaoConfig myDaoConfig;
boolean myDontUseHashesForSearch;
final BaseHapiFhirDao<?> myCallingDao;
final CriteriaBuilder myBuilder;
final Root<ResourceTable> myResourceTableRoot;
final IndexJoins myIndexJoins;
final ArrayList<Predicate> myPredicates;
final Class<? extends IBaseResource> myResourceType;
final String myResourceName;
final AbstractQuery<Long> myResourceTableQuery;
final SearchParameterMap myParams;
// FIXME KHS autowire with lookup
BasePredicateBuilder(SearchBuilder theSearchBuilder) {
myCallingDao = theSearchBuilder.getCallingDao();
myBuilder = theSearchBuilder.getBuilder();
myResourceTableRoot = theSearchBuilder.getResourceTableRoot();
myIndexJoins = theSearchBuilder.getIndexJoins();
myPredicates = theSearchBuilder.getPredicates();
myResourceType = theSearchBuilder.getResourceType();
myResourceName = theSearchBuilder.getResourceName();
myResourceTableQuery = theSearchBuilder.getResourceTableQuery();
myParams = theSearchBuilder.getParams();
}
@PostConstruct
private void postConstruct() {
myDontUseHashesForSearch = myDaoConfig.getDisableHashBasedSearches();
}
@SuppressWarnings("unchecked")
<T> Join<ResourceTable, T> createJoin(SearchBuilderJoinEnum 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;
case COORDS:
join = myResourceTableRoot.join("myParamsCoords", JoinType.LEFT);
break;
}
SearchBuilderJoinKey key = new SearchBuilderJoinKey(theSearchParameterName, theType);
myIndexJoins.put(key, join);
return (Join<ResourceTable, T>) join;
}
void addPredicateParamMissing(String theResourceName, String theParamName, boolean theMissing) {
// if (myDontUseHashesForSearch) {
// Join<ResourceTable, SearchParamPresent> paramPresentJoin = myResourceTableRoot.join("mySearchParamPresents", JoinType.LEFT);
// Join<Object, Object> paramJoin = paramPresentJoin.join("mySearchParam", JoinType.LEFT);
//
// myPredicates.add(myBuilder.equal(paramJoin.get("myResourceName"), theResourceName));
// myPredicates.add(myBuilder.equal(paramJoin.get("myParamName"), theParamName));
// myPredicates.add(myBuilder.equal(paramPresentJoin.get("myPresent"), !theMissing));
// }
Join<ResourceTable, SearchParamPresent> paramPresentJoin = myResourceTableRoot.join("mySearchParamPresents", JoinType.LEFT);
Expression<Long> hashPresence = paramPresentJoin.get("myHashPresence").as(Long.class);
Long hash = SearchParamPresent.calculateHashPresence(theResourceName, theParamName, !theMissing);
myPredicates.add(myBuilder.equal(hashPresence, hash));
}
void addPredicateParamMissing(String theResourceName, String theParamName, boolean theMissing, Join<ResourceTable, ? extends BaseResourceIndexedSearchParam> theJoin) {
myPredicates.add(myBuilder.equal(theJoin.get("myResourceType"), theResourceName));
myPredicates.add(myBuilder.equal(theJoin.get("myParamName"), theParamName));
myPredicates.add(myBuilder.equal(theJoin.get("myMissing"), theMissing));
}
Predicate combineParamIndexPredicateWithParamNamePredicate(String theResourceName, String theParamName, From<?, ? extends BaseResourceIndexedSearchParam> theFrom, Predicate thePredicate) {
if (myDontUseHashesForSearch) {
Predicate resourceTypePredicate = myBuilder.equal(theFrom.get("myResourceType"), theResourceName);
Predicate paramNamePredicate = myBuilder.equal(theFrom.get("myParamName"), theParamName);
Predicate outerPredicate = myBuilder.and(resourceTypePredicate, paramNamePredicate, thePredicate);
return outerPredicate;
}
long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(theResourceName, theParamName);
Predicate hashIdentityPredicate = myBuilder.equal(theFrom.get("myHashIdentity"), hashIdentity);
return myBuilder.and(hashIdentityPredicate, thePredicate);
}
Predicate createPredicateNumeric(String theResourceName,
String theParamName,
From<?, ? extends BaseResourceIndexedSearchParam> theFrom,
CriteriaBuilder builder,
IQueryParameterType theParam,
ParamPrefixEnum thePrefix,
BigDecimal theValue,
final Expression<BigDecimal> thePath,
String invalidMessageName) {
Predicate num;
// Per discussions with Grahame Grieve and James Agnew on 11/13/19, modified logic for EQUAL and NOT_EQUAL operators below so as to
// use exact value matching. The "fuzz amount" matching is still used with the APPROXIMATE operator.
switch (thePrefix) {
case GREATERTHAN:
num = builder.gt(thePath, theValue);
break;
case GREATERTHAN_OR_EQUALS:
num = builder.ge(thePath, theValue);
break;
case LESSTHAN:
num = builder.lt(thePath, theValue);
break;
case LESSTHAN_OR_EQUALS:
num = builder.le(thePath, theValue);
break;
case EQUAL:
num = builder.equal(thePath, theValue);
break;
case NOT_EQUAL:
num = builder.notEqual(thePath, theValue);
break;
case APPROXIMATE:
BigDecimal mul = calculateFuzzAmount(thePrefix, theValue);
BigDecimal low = theValue.subtract(mul, MathContext.DECIMAL64);
BigDecimal high = theValue.add(mul, MathContext.DECIMAL64);
Predicate lowPred;
Predicate highPred;
lowPred = builder.ge(thePath.as(BigDecimal.class), low);
highPred = builder.le(thePath.as(BigDecimal.class), high);
num = builder.and(lowPred, highPred);
ourLog.trace("Searching for {} <= val <= {}", low, high);
break;
case ENDS_BEFORE:
case STARTS_AFTER:
default:
String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, invalidMessageName, thePrefix.getValue(), theParam.getValueAsQueryToken(myContext));
throw new InvalidRequestException(msg);
}
if (theParamName == null) {
return num;
}
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, num);
}
/**
* Figures out the tolerance for a search. For example, if the user is searching for <code>4.00</code>, this method
* returns <code>0.005</code> because we shold actually match values which are
* <code>4 (+/-) 0.005</code> according to the FHIR specs.
*/
static BigDecimal calculateFuzzAmount(ParamPrefixEnum cmpValue, BigDecimal theValue) {
if (cmpValue == ParamPrefixEnum.APPROXIMATE) {
return theValue.multiply(new BigDecimal(0.1));
} else {
String plainString = theValue.toPlainString();
int dotIdx = plainString.indexOf('.');
if (dotIdx == -1) {
return new BigDecimal(0.5);
}
int precision = plainString.length() - (dotIdx);
double mul = Math.pow(10, -precision);
double val = mul * 5.0d;
return new BigDecimal(val);
}
}
static String createLeftAndRightMatchLikeExpression(String likeExpression) {
return "%" + likeExpression.replace("%", "[%]") + "%";
}
static String createLeftMatchLikeExpression(String likeExpression) {
return likeExpression.replace("%", "[%]") + "%";
}
static String createRightMatchLikeExpression(String likeExpression) {
return "%" + likeExpression.replace("%", "[%]");
}
static Predicate[] toArray(List<Predicate> thePredicates) {
return thePredicates.toArray(new Predicate[0]);
}
}

View File

@ -0,0 +1,24 @@
package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import com.google.common.collect.Maps;
import javax.persistence.criteria.Join;
import java.util.Map;
public class IndexJoins {
Map<SearchBuilderJoinKey, Join<?, ?>> myIndexJoins = Maps.newHashMap();
public void put(SearchBuilderJoinKey theKey, Join<ResourceTable, ResourceIndexedSearchParamDate> theJoin) {
myIndexJoins.put(theKey, theJoin);
}
public boolean haveIndexJoins() {
return !myIndexJoins.isEmpty();
}
public Join<?,?> get(SearchBuilderJoinKey theKey) {
myIndexJoins.get(theKey);
}
}

View File

@ -0,0 +1,135 @@
package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.util.CoordCalculator;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.dstu2.resource.Location;
import ca.uhn.fhir.rest.param.QuantityParam;
import ca.uhn.fhir.rest.param.SpecialParam;
import ca.uhn.fhir.rest.param.TokenParam;
import org.hibernate.search.spatial.impl.Point;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.isBlank;
public class PredicateBuilderCoords extends BasePredicateBuilder {
PredicateBuilderCoords(SearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}
private Predicate createPredicateCoords(IQueryParameterType theParam,
String theResourceName,
String theParamName,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamCoords> theFrom) {
String latitudeValue;
String longitudeValue;
Double distanceKm = 0.0;
if (theParam instanceof TokenParam) { // DSTU3
TokenParam param = (TokenParam) theParam;
String value = param.getValue();
String[] parts = value.split(":");
if (parts.length != 2) {
throw new IllegalArgumentException("Invalid position format '" + value + "'. Required format is 'latitude:longitude'");
}
latitudeValue = parts[0];
longitudeValue = parts[1];
if (isBlank(latitudeValue) || isBlank(longitudeValue)) {
throw new IllegalArgumentException("Invalid position format '" + value + "'. Both latitude and longitude must be provided.");
}
QuantityParam distanceParam = myParams.getNearDistanceParam();
if (distanceParam != null) {
distanceKm = distanceParam.getValue().doubleValue();
}
} else if (theParam instanceof SpecialParam) { // R4
SpecialParam param = (SpecialParam) theParam;
String value = param.getValue();
String[] parts = value.split("\\|");
if (parts.length < 2 || parts.length > 4) {
throw new IllegalArgumentException("Invalid position format '" + value + "'. Required format is 'latitude|longitude' or 'latitude|longitude|distance' or 'latitude|longitude|distance|units'");
}
latitudeValue = parts[0];
longitudeValue = parts[1];
if (isBlank(latitudeValue) || isBlank(longitudeValue)) {
throw new IllegalArgumentException("Invalid position format '" + value + "'. Both latitude and longitude must be provided.");
}
if (parts.length >= 3) {
String distanceString = parts[2];
if (!isBlank(distanceString)) {
distanceKm = Double.valueOf(distanceString);
}
}
} else {
throw new IllegalArgumentException("Invalid position type: " + theParam.getClass());
}
Predicate latitudePredicate;
Predicate longitudePredicate;
if (distanceKm == 0.0) {
latitudePredicate = theBuilder.equal(theFrom.get("myLatitude"), latitudeValue);
longitudePredicate = theBuilder.equal(theFrom.get("myLongitude"), longitudeValue);
} else if (distanceKm < 0.0) {
throw new IllegalArgumentException("Invalid " + Location.SP_NEAR_DISTANCE + " parameter '" + distanceKm + "' must be >= 0.0");
} else {
Double latitudeDegrees = Double.valueOf(latitudeValue);
Double longitudeDegrees = Double.valueOf(longitudeValue);
Point northPoint = CoordCalculator.findTarget(latitudeDegrees, longitudeDegrees, 0.0, distanceKm);
Point eastPoint = CoordCalculator.findTarget(latitudeDegrees, longitudeDegrees, 90.0, distanceKm);
Point southPoint = CoordCalculator.findTarget(latitudeDegrees, longitudeDegrees, 180.0, distanceKm);
Point westPoint = CoordCalculator.findTarget(latitudeDegrees, longitudeDegrees, 270.0, distanceKm);
latitudePredicate = theBuilder.and(
theBuilder.greaterThanOrEqualTo(theFrom.get("myLatitude"), southPoint.getLatitude()),
theBuilder.lessThanOrEqualTo(theFrom.get("myLatitude"), northPoint.getLatitude())
);
longitudePredicate = theBuilder.and(
theBuilder.greaterThanOrEqualTo(theFrom.get("myLongitude"), westPoint.getLongitude()),
theBuilder.lessThanOrEqualTo(theFrom.get("myLongitude"), eastPoint.getLongitude())
);
}
Predicate singleCode = theBuilder.and(latitudePredicate, longitudePredicate);
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode);
}
public Predicate addPredicateCoords(String theResourceName,
String theParamName,
List<? extends IQueryParameterType> theList) {
Join<ResourceTable, ResourceIndexedSearchParamCoords> join = createJoin(SearchBuilderJoinEnum.COORDS, theParamName);
if (theList.get(0).getMissing() != null) {
addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
return null;
}
List<Predicate> codePredicates = new ArrayList<Predicate>();
for (IQueryParameterType nextOr : theList) {
Predicate singleCode = createPredicateCoords(nextOr,
theResourceName,
theParamName,
myBuilder,
join
);
codePredicates.add(singleCode);
}
Predicate retVal = myBuilder.or(toArray(codePredicates));
myPredicates.add(retVal);
return retVal;
}
}

View File

@ -0,0 +1,193 @@
package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchFilterParser;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.api.IQueryParameterType;
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.server.exceptions.InvalidRequestException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class PredicateBuilderDate extends BasePredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderDate.class);
PredicateBuilderDate(SearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}
public Predicate addPredicateDate(String theResourceName,
String theParamName,
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation operation) {
Join<ResourceTable, ResourceIndexedSearchParamDate> join = createJoin(SearchBuilderJoinEnum.DATE, theParamName);
if (theList.get(0).getMissing() != null) {
Boolean missing = theList.get(0).getMissing();
addPredicateParamMissing(theResourceName, theParamName, missing, join);
return null;
}
List<Predicate> codePredicates = new ArrayList<>();
for (IQueryParameterType nextOr : theList) {
IQueryParameterType params = nextOr;
Predicate p = createPredicateDate(params,
theResourceName,
theParamName,
myBuilder,
join,
operation);
codePredicates.add(p);
}
Predicate orPredicates = myBuilder.or(toArray(codePredicates));
myPredicates.add(orPredicates);
return orPredicates;
}
public Predicate createPredicateDate(IQueryParameterType theParam,
String theResourceName,
String theParamName,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamDate> theFrom) {
return createPredicateDate(theParam,
theResourceName,
theParamName,
theBuilder,
theFrom,
null);
}
private Predicate createPredicateDate(IQueryParameterType theParam,
String theResourceName,
String theParamName,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamDate> theFrom,
SearchFilterParser.CompareOperation operation) {
Predicate p;
if (theParam instanceof DateParam) {
DateParam date = (DateParam) theParam;
if (!date.isEmpty()) {
DateRangeParam range = new DateRangeParam(date);
p = createPredicateDateFromRange(theBuilder,
theFrom,
range,
operation);
} else {
// TODO: handle missing date param?
p = null;
}
} else if (theParam instanceof DateRangeParam) {
DateRangeParam range = (DateRangeParam) theParam;
p = createPredicateDateFromRange(theBuilder,
theFrom,
range,
operation);
} else {
throw new IllegalArgumentException("Invalid token type: " + theParam.getClass());
}
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, p);
}
private Predicate createPredicateDateFromRange(CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamDate> theFrom,
DateRangeParam theRange,
SearchFilterParser.CompareOperation operation) {
Date lowerBound = theRange.getLowerBoundAsInstant();
Date upperBound = theRange.getUpperBoundAsInstant();
Predicate lt = null;
Predicate gt = null;
Predicate lb = null;
Predicate ub = null;
if (operation == SearchFilterParser.CompareOperation.lt) {
if (lowerBound == null) {
throw new InvalidRequestException("lowerBound value not correctly specified for compare operation");
}
lb = theBuilder.lessThan(theFrom.get("myValueLow"), lowerBound);
} else if (operation == SearchFilterParser.CompareOperation.le) {
if (upperBound == null) {
throw new InvalidRequestException("upperBound value not correctly specified for compare operation");
}
lb = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHigh"), upperBound);
} else if (operation == SearchFilterParser.CompareOperation.gt) {
if (upperBound == null) {
throw new InvalidRequestException("upperBound value not correctly specified for compare operation");
}
lb = theBuilder.greaterThan(theFrom.get("myValueHigh"), upperBound);
} else if (operation == SearchFilterParser.CompareOperation.ge) {
if (lowerBound == null) {
throw new InvalidRequestException("lowerBound value not correctly specified for compare operation");
}
lb = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLow"), lowerBound);
} else if (operation == SearchFilterParser.CompareOperation.ne) {
if ((lowerBound == null) ||
(upperBound == null)) {
throw new InvalidRequestException("lowerBound and/or upperBound value not correctly specified for compare operation");
}
/*Predicate*/
lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLow"), lowerBound);
/*Predicate*/
gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHigh"), upperBound);
lb = theBuilder.or(lt,
gt);
} else if ((operation == SearchFilterParser.CompareOperation.eq) ||
(operation == null)) {
if (lowerBound != null) {
/*Predicate*/
gt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueLow"), lowerBound);
/*Predicate*/
lt = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueHigh"), lowerBound);
if (theRange.getLowerBound().getPrefix() == ParamPrefixEnum.STARTS_AFTER || theRange.getLowerBound().getPrefix() == ParamPrefixEnum.EQUAL) {
lb = gt;
} else {
lb = theBuilder.or(gt, lt);
}
}
if (upperBound != null) {
/*Predicate*/
gt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueLow"), upperBound);
/*Predicate*/
lt = theBuilder.lessThanOrEqualTo(theFrom.get("myValueHigh"), upperBound);
if (theRange.getUpperBound().getPrefix() == ParamPrefixEnum.ENDS_BEFORE || theRange.getUpperBound().getPrefix() == ParamPrefixEnum.EQUAL) {
ub = lt;
} else {
ub = theBuilder.or(gt, lt);
}
}
} else {
throw new InvalidRequestException(String.format("Unsupported operator specified, operator=%s",
operation.name()));
}
ourLog.trace("Date range is {} - {}", lowerBound, upperBound);
if (lb != null && ub != null) {
return (theBuilder.and(lb, ub));
} else if (lb != null) {
return (lb);
} else {
return (ub);
}
}
}

View File

@ -0,0 +1,25 @@
package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import org.springframework.beans.factory.annotation.Lookup;
import org.springframework.stereotype.Service;
@Service
public abstract class PredicateBuilderFactory {
@Lookup
public abstract PredicateBuilderCoords newPredicateBuilderCoords(SearchBuilder theSearchBuilder);
@Lookup
public abstract PredicateBuilderDate newPredicateBuilderDate(SearchBuilder theSearchBuilder);
@Lookup
public abstract PredicateBuilderNumber newPredicateBuilderNumber(SearchBuilder theSearchBuilder);
@Lookup
public abstract PredicateBuilderQuantity newPredicateBuilderQuantity(SearchBuilder theSearchBuilder);
@Lookup
public abstract PredicateBuilderString newPredicateBuilderString(SearchBuilder theSearchBuilder);
@Lookup
public abstract PredicateBuilderTag newPredicateBuilderTag(SearchBuilder theSearchBuilder);
@Lookup
public abstract PredicateBuilderToken newPredicateBuilderToken(SearchBuilder theSearchBuilder);
@Lookup
public abstract PredicateBuilderUri newPredicateBuilderUri(SearchBuilder theSearchBuilder);
}

View File

@ -0,0 +1,88 @@
package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchFilterParser;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.NumberParam;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.criteria.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
public class PredicateBuilderNumber extends BasePredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderNumber.class);
PredicateBuilderNumber(SearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}
public Predicate addPredicateNumber(String theResourceName,
String theParamName,
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation operation) {
Join<ResourceTable, ResourceIndexedSearchParamNumber> join = createJoin(SearchBuilderJoinEnum.NUMBER, theParamName);
if (theList.get(0).getMissing() != null) {
addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
return null;
}
List<Predicate> codePredicates = new ArrayList<>();
for (IQueryParameterType nextOr : theList) {
if (nextOr instanceof NumberParam) {
NumberParam param = (NumberParam) nextOr;
BigDecimal value = param.getValue();
if (value == null) {
continue;
}
final Expression<BigDecimal> fromObj = join.get("myValue");
ParamPrefixEnum prefix = defaultIfNull(param.getPrefix(), ParamPrefixEnum.EQUAL);
if (operation == SearchFilterParser.CompareOperation.ne) {
prefix = ParamPrefixEnum.NOT_EQUAL;
} else if (operation == SearchFilterParser.CompareOperation.lt) {
prefix = ParamPrefixEnum.LESSTHAN;
} else if (operation == SearchFilterParser.CompareOperation.le) {
prefix = ParamPrefixEnum.LESSTHAN_OR_EQUALS;
} else if (operation == SearchFilterParser.CompareOperation.gt) {
prefix = ParamPrefixEnum.GREATERTHAN;
} else if (operation == SearchFilterParser.CompareOperation.ge) {
prefix = ParamPrefixEnum.GREATERTHAN_OR_EQUALS;
} else if (operation == SearchFilterParser.CompareOperation.eq) {
prefix = ParamPrefixEnum.EQUAL;
} else if (operation != null) {
throw new IllegalArgumentException("Invalid operator specified for number type");
}
String invalidMessageName = "invalidNumberPrefix";
Predicate predicateNumeric = createPredicateNumeric(theResourceName, theParamName, join, myBuilder, nextOr, prefix, value, fromObj, invalidMessageName);
Predicate predicateOuter = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, predicateNumeric);
codePredicates.add(predicateOuter);
} else {
throw new IllegalArgumentException("Invalid token type: " + nextOr.getClass());
}
}
Predicate predicate = myBuilder.or(toArray(codePredicates));
myPredicates.add(predicate);
return predicate;
}
}

View File

@ -0,0 +1,171 @@
package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchFilterParser;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.base.composite.BaseQuantityDt;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.param.QuantityParam;
import javax.persistence.criteria.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
import static org.apache.commons.lang3.StringUtils.isBlank;
public class PredicateBuilderQuantity extends BasePredicateBuilder {
PredicateBuilderQuantity(SearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}
public Predicate addPredicateQuantity(String theResourceName,
String theParamName,
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation operation) {
Join<ResourceTable, ResourceIndexedSearchParamQuantity> join = createJoin(SearchBuilderJoinEnum.QUANTITY, theParamName);
if (theList.get(0).getMissing() != null) {
addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
return null;
}
List<Predicate> codePredicates = new ArrayList<Predicate>();
for (IQueryParameterType nextOr : theList) {
Predicate singleCode = createPredicateQuantity(nextOr,
theResourceName,
theParamName,
myBuilder,
join,
operation);
codePredicates.add(singleCode);
}
Predicate retVal = myBuilder.or(toArray(codePredicates));
myPredicates.add(retVal);
return retVal;
}
public Predicate createPredicateQuantity(IQueryParameterType theParam,
String theResourceName,
String theParamName,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamQuantity> theFrom) {
return createPredicateQuantity(theParam,
theResourceName,
theParamName,
theBuilder,
theFrom,
null);
}
private Predicate createPredicateQuantity(IQueryParameterType theParam,
String theResourceName,
String theParamName,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamQuantity> theFrom,
SearchFilterParser.CompareOperation operation) {
String systemValue;
String unitsValue;
ParamPrefixEnum cmpValue = null;
BigDecimal valueValue;
if (operation == SearchFilterParser.CompareOperation.ne) {
cmpValue = ParamPrefixEnum.NOT_EQUAL;
} else if (operation == SearchFilterParser.CompareOperation.lt) {
cmpValue = ParamPrefixEnum.LESSTHAN;
} else if (operation == SearchFilterParser.CompareOperation.le) {
cmpValue = ParamPrefixEnum.LESSTHAN_OR_EQUALS;
} else if (operation == SearchFilterParser.CompareOperation.gt) {
cmpValue = ParamPrefixEnum.GREATERTHAN;
} else if (operation == SearchFilterParser.CompareOperation.ge) {
cmpValue = ParamPrefixEnum.GREATERTHAN_OR_EQUALS;
} else if (operation == SearchFilterParser.CompareOperation.eq) {
cmpValue = ParamPrefixEnum.EQUAL;
} else if (operation != null) {
throw new IllegalArgumentException("Invalid operator specified for quantity type");
}
if (theParam instanceof BaseQuantityDt) {
BaseQuantityDt param = (BaseQuantityDt) theParam;
systemValue = param.getSystemElement().getValueAsString();
unitsValue = param.getUnitsElement().getValueAsString();
if (operation == null) {
cmpValue = ParamPrefixEnum.forValue(param.getComparatorElement().getValueAsString());
}
valueValue = param.getValueElement().getValue();
} else if (theParam instanceof QuantityParam) {
QuantityParam param = (QuantityParam) theParam;
systemValue = param.getSystem();
unitsValue = param.getUnits();
if (operation == null) {
cmpValue = param.getPrefix();
}
valueValue = param.getValue();
} else {
throw new IllegalArgumentException("Invalid quantity type: " + theParam.getClass());
}
if (myDontUseHashesForSearch) {
Predicate system = null;
if (!isBlank(systemValue)) {
system = theBuilder.equal(theFrom.get("mySystem"), systemValue);
}
Predicate code = null;
if (!isBlank(unitsValue)) {
code = theBuilder.equal(theFrom.get("myUnits"), unitsValue);
}
cmpValue = defaultIfNull(cmpValue, ParamPrefixEnum.EQUAL);
final Expression<BigDecimal> path = theFrom.get("myValue");
String invalidMessageName = "invalidQuantityPrefix";
Predicate num = createPredicateNumeric(theResourceName, null, theFrom, theBuilder, theParam, cmpValue, valueValue, path, invalidMessageName);
Predicate singleCode;
if (system == null && code == null) {
singleCode = num;
} else if (system == null) {
singleCode = theBuilder.and(code, num);
} else if (code == null) {
singleCode = theBuilder.and(system, num);
} else {
singleCode = theBuilder.and(system, code, num);
}
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode);
}
Predicate hashPredicate;
if (!isBlank(systemValue) && !isBlank(unitsValue)) {
long hash = ResourceIndexedSearchParamQuantity.calculateHashSystemAndUnits(theResourceName, theParamName, systemValue, unitsValue);
hashPredicate = myBuilder.equal(theFrom.get("myHashIdentitySystemAndUnits"), hash);
} else if (!isBlank(unitsValue)) {
long hash = ResourceIndexedSearchParamQuantity.calculateHashUnits(theResourceName, theParamName, unitsValue);
hashPredicate = myBuilder.equal(theFrom.get("myHashIdentityAndUnits"), hash);
} else {
long hash = BaseResourceIndexedSearchParam.calculateHashIdentity(theResourceName, theParamName);
hashPredicate = myBuilder.equal(theFrom.get("myHashIdentity"), hash);
}
cmpValue = defaultIfNull(cmpValue, ParamPrefixEnum.EQUAL);
final Expression<BigDecimal> path = theFrom.get("myValue");
String invalidMessageName = "invalidQuantityPrefix";
Predicate numericPredicate = createPredicateNumeric(theResourceName, null, theFrom, theBuilder, theParam, cmpValue, valueValue, path, invalidMessageName);
return theBuilder.and(hashPredicate, numericPredicate);
}
}

View File

@ -0,0 +1,196 @@
package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchFilterParser;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.util.StringNormalizer;
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
public class PredicateBuilderString extends BasePredicateBuilder {
PredicateBuilderString(SearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}
public Predicate addPredicateString(String theResourceName,
String theParamName,
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation operation) {
Join<ResourceTable, ResourceIndexedSearchParamString> join = createJoin(SearchBuilderJoinEnum.STRING, theParamName);
if (theList.get(0).getMissing() != null) {
addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
return null;
}
List<Predicate> codePredicates = new ArrayList<>();
for (IQueryParameterType nextOr : theList) {
IQueryParameterType theParameter = nextOr;
Predicate singleCode = createPredicateString(theParameter,
theResourceName,
theParamName,
myBuilder,
join,
operation);
codePredicates.add(singleCode);
}
Predicate retVal = myBuilder.or(toArray(codePredicates));
myPredicates.add(retVal);
return retVal;
}
public Predicate createPredicateString(IQueryParameterType theParameter,
String theResourceName,
String theParamName,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamString> theFrom) {
return createPredicateString(theParameter,
theResourceName,
theParamName,
theBuilder,
theFrom,
null);
}
private Predicate createPredicateString(IQueryParameterType theParameter,
String theResourceName,
String theParamName,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamString> theFrom,
SearchFilterParser.CompareOperation operation) {
String rawSearchTerm;
if (theParameter instanceof TokenParam) {
TokenParam id = (TokenParam) theParameter;
if (!id.isText()) {
throw new IllegalStateException("Trying to process a text search on a non-text token parameter");
}
rawSearchTerm = id.getValue();
} else if (theParameter instanceof StringParam) {
StringParam id = (StringParam) theParameter;
rawSearchTerm = id.getValue();
if (id.isContains()) {
if (!myDaoConfig.isAllowContainsSearches()) {
throw new MethodNotAllowedException(":contains modifier is disabled on this server");
}
}
} else if (theParameter instanceof IPrimitiveDatatype<?>) {
IPrimitiveDatatype<?> id = (IPrimitiveDatatype<?>) theParameter;
rawSearchTerm = id.getValueAsString();
} else {
throw new IllegalArgumentException("Invalid token type: " + theParameter.getClass());
}
if (rawSearchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
throw new InvalidRequestException("Parameter[" + theParamName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed ("
+ ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm);
}
if (myDontUseHashesForSearch) {
String likeExpression = StringNormalizer.normalizeString(rawSearchTerm);
if (myDaoConfig.isAllowContainsSearches()) {
if (theParameter instanceof StringParam) {
if (((StringParam) theParameter).isContains()) {
likeExpression = createLeftAndRightMatchLikeExpression(likeExpression);
} else {
likeExpression = createLeftMatchLikeExpression(likeExpression);
}
} else {
likeExpression = createLeftMatchLikeExpression(likeExpression);
}
} else {
likeExpression = createLeftMatchLikeExpression(likeExpression);
}
Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression);
if (theParameter instanceof StringParam && ((StringParam) theParameter).isExact()) {
Predicate exactCode = theBuilder.equal(theFrom.get("myValueExact"), rawSearchTerm);
singleCode = theBuilder.and(singleCode, exactCode);
}
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode);
}
boolean exactMatch = theParameter instanceof StringParam && ((StringParam) theParameter).isExact();
if (exactMatch) {
// Exact match
Long hash = ResourceIndexedSearchParamString.calculateHashExact(theResourceName, theParamName, rawSearchTerm);
return theBuilder.equal(theFrom.get("myHashExact").as(Long.class), hash);
} else {
// Normalized Match
String normalizedString = StringNormalizer.normalizeString(rawSearchTerm);
String likeExpression;
if ((theParameter instanceof StringParam) &&
(((((StringParam) theParameter).isContains()) &&
(myCallingDao.getConfig().isAllowContainsSearches())) ||
(operation == SearchFilterParser.CompareOperation.co))) {
likeExpression = createLeftAndRightMatchLikeExpression(normalizedString);
} else if ((operation != SearchFilterParser.CompareOperation.ne) &&
(operation != SearchFilterParser.CompareOperation.gt) &&
(operation != SearchFilterParser.CompareOperation.lt) &&
(operation != SearchFilterParser.CompareOperation.ge) &&
(operation != SearchFilterParser.CompareOperation.le)) {
if (operation == SearchFilterParser.CompareOperation.ew) {
likeExpression = createRightMatchLikeExpression(normalizedString);
} else {
likeExpression = createLeftMatchLikeExpression(normalizedString);
}
} else {
likeExpression = normalizedString;
}
Predicate predicate;
if ((operation == null) ||
(operation == SearchFilterParser.CompareOperation.sw)) {
Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(myDaoConfig.getModelConfig(), theResourceName, theParamName, normalizedString);
Predicate hashCode = theBuilder.equal(theFrom.get("myHashNormalizedPrefix").as(Long.class), hash);
Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression);
predicate = theBuilder.and(hashCode, singleCode);
} else if ((operation == SearchFilterParser.CompareOperation.ew) ||
(operation == SearchFilterParser.CompareOperation.co)) {
Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode);
} else if (operation == SearchFilterParser.CompareOperation.eq) {
Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(myDaoConfig.getModelConfig(), theResourceName, theParamName, normalizedString);
Predicate hashCode = theBuilder.equal(theFrom.get("myHashNormalizedPrefix").as(Long.class), hash);
Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), normalizedString);
predicate = theBuilder.and(hashCode, singleCode);
} else if (operation == SearchFilterParser.CompareOperation.ne) {
Predicate singleCode = theBuilder.notEqual(theFrom.get("myValueNormalized").as(String.class), likeExpression);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode);
} else if (operation == SearchFilterParser.CompareOperation.gt) {
Predicate singleCode = theBuilder.greaterThan(theFrom.get("myValueNormalized").as(String.class), likeExpression);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode);
} else if (operation == SearchFilterParser.CompareOperation.lt) {
Predicate singleCode = theBuilder.lessThan(theFrom.get("myValueNormalized").as(String.class), likeExpression);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode);
} else if (operation == SearchFilterParser.CompareOperation.ge) {
Predicate singleCode = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueNormalized").as(String.class), likeExpression);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode);
} else if (operation == SearchFilterParser.CompareOperation.le) {
Predicate singleCode = theBuilder.lessThanOrEqualTo(theFrom.get("myValueNormalized").as(String.class), likeExpression);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode);
} else {
throw new IllegalArgumentException("Don't yet know how to handle operation " + operation + " on a string");
}
return predicate;
}
}
}

View File

@ -0,0 +1,179 @@
package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTag;
import ca.uhn.fhir.jpa.model.entity.TagDefinition;
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.Constants;
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.server.exceptions.InvalidRequestException;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.criteria.*;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class PredicateBuilderTag extends BasePredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderTag.class);
PredicateBuilderTag(SearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}
public void addPredicateTag(List<List<IQueryParameterType>> theList, String theParamName) {
TagTypeEnum tagType;
if (Constants.PARAM_TAG.equals(theParamName)) {
tagType = TagTypeEnum.TAG;
} else if (Constants.PARAM_PROFILE.equals(theParamName)) {
tagType = TagTypeEnum.PROFILE;
} else if (Constants.PARAM_SECURITY.equals(theParamName)) {
tagType = TagTypeEnum.SECURITY_LABEL;
} else {
throw new IllegalArgumentException("Param name: " + theParamName); // shouldn't happen
}
List<Pair<String, String>> notTags = Lists.newArrayList();
for (List<? extends IQueryParameterType> nextAndParams : theList) {
for (IQueryParameterType nextOrParams : nextAndParams) {
if (nextOrParams instanceof TokenParam) {
TokenParam param = (TokenParam) nextOrParams;
if (param.getModifier() == TokenParamModifier.NOT) {
if (isNotBlank(param.getSystem()) || isNotBlank(param.getValue())) {
notTags.add(Pair.of(param.getSystem(), param.getValue()));
}
}
}
}
}
/*
* We have a parameter of ResourceType?_tag:not=foo This means match resources that don't have the given tag(s)
*/
if (notTags.isEmpty() == false) {
// 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));
//
// Subquery<Long> subQ = cq.subquery(Long.class);
// Root<ResourceTag> subQfrom = subQ.from(ResourceTag.class);
// subQ.select(subQfrom.get("myResourceId").as(Long.class));
// Predicate subQname = builder.equal(subQfrom.get("myParamName"), theParamName);
// Predicate subQtype = builder.equal(subQfrom.get("myResourceType"), myResourceName);
// subQ.where(builder.and(subQtype, subQname));
//
// List<Predicate> predicates = new ArrayList<Predicate>();
// predicates.add(builder.not(builder.in(from.get("myId")).value(subQ)));
// predicates.add(builder.equal(from.get("myResourceType"), myResourceName));
// predicates.add(builder.isNull(from.get("myDeleted")));
// createPredicateResourceId(builder, cq, predicates, from.get("myId").as(Long.class));
}
for (List<? extends IQueryParameterType> nextAndParams : theList) {
boolean haveTags = false;
for (IQueryParameterType nextParamUncasted : nextAndParams) {
if (nextParamUncasted instanceof TokenParam) {
TokenParam nextParam = (TokenParam) nextParamUncasted;
if (isNotBlank(nextParam.getValue())) {
haveTags = true;
} else if (isNotBlank(nextParam.getSystem())) {
throw new InvalidRequestException("Invalid " + theParamName + " parameter (must supply a value/code and not just a system): " + nextParam.getValueAsQueryToken(myContext));
}
} else {
UriParam nextParam = (UriParam) nextParamUncasted;
if (isNotBlank(nextParam.getValue())) {
haveTags = true;
}
}
}
if (!haveTags) {
continue;
}
boolean paramInverted = false;
List<Pair<String, String>> tokens = Lists.newArrayList();
for (IQueryParameterType nextOrParams : nextAndParams) {
String code;
String system;
if (nextOrParams instanceof TokenParam) {
TokenParam nextParam = (TokenParam) nextOrParams;
code = nextParam.getValue();
system = nextParam.getSystem();
if (nextParam.getModifier() == TokenParamModifier.NOT) {
paramInverted = true;
}
} else {
UriParam nextParam = (UriParam) nextOrParams;
code = nextParam.getValue();
system = null;
}
if (isNotBlank(code)) {
tokens.add(Pair.of(system, code));
}
}
if (tokens.isEmpty()) {
continue;
}
if (paramInverted) {
ourLog.debug("Searching for _tag:not");
Subquery<Long> subQ = myResourceTableQuery.subquery(Long.class);
Root<ResourceTag> subQfrom = subQ.from(ResourceTag.class);
subQ.select(subQfrom.get("myResourceId").as(Long.class));
myPredicates.add(myBuilder.not(myBuilder.in(myResourceTableRoot.get("myId")).value(subQ)));
Subquery<Long> defJoin = subQ.subquery(Long.class);
Root<TagDefinition> defJoinFrom = defJoin.from(TagDefinition.class);
defJoin.select(defJoinFrom.get("myId").as(Long.class));
subQ.where(subQfrom.get("myTagId").as(Long.class).in(defJoin));
Predicate tagListPredicate = createPredicateTagList(defJoinFrom, myBuilder, tagType, tokens);
defJoin.where(tagListPredicate);
continue;
}
Join<ResourceTable, ResourceTag> tagJoin = myResourceTableRoot.join("myTags", JoinType.LEFT);
From<ResourceTag, TagDefinition> defJoin = tagJoin.join("myTag");
Predicate tagListPredicate = createPredicateTagList(defJoin, myBuilder, tagType, tokens);
myPredicates.add(tagListPredicate);
}
}
private Predicate createPredicateTagList(Path<TagDefinition> theDefJoin, CriteriaBuilder theBuilder, TagTypeEnum theTagType, List<Pair<String, String>> theTokens) {
Predicate typePredicate = theBuilder.equal(theDefJoin.get("myTagType"), theTagType);
List<Predicate> orPredicates = Lists.newArrayList();
for (Pair<String, String> next : theTokens) {
Predicate codePredicate = theBuilder.equal(theDefJoin.get("myCode"), next.getRight());
if (isNotBlank(next.getLeft())) {
Predicate systemPredicate = theBuilder.equal(theDefJoin.get("mySystem"), next.getLeft());
orPredicates.add(theBuilder.and(typePredicate, systemPredicate, codePredicate));
} else {
orPredicates.add(theBuilder.and(typePredicate, codePredicate));
}
}
return theBuilder.or(toArray(orPredicates));
}
}

View File

@ -0,0 +1,325 @@
package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchFilterParser;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.term.VersionIndependentConcept;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
import ca.uhn.fhir.model.base.composite.BaseIdentifierDt;
import ca.uhn.fhir.rest.param.NumberParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.TokenParamModifier;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import com.google.common.collect.Sets;
import org.hibernate.query.criteria.internal.CriteriaBuilderImpl;
import org.hibernate.query.criteria.internal.predicate.BooleanStaticAssertionPredicate;
import org.springframework.beans.factory.annotation.Autowired;
import javax.persistence.criteria.*;
import java.util.*;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
public class PredicateBuilderToken extends BasePredicateBuilder {
@Autowired
private ITermReadSvc myTerminologySvc;
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
private final PredicateBuilderString myPredicateBuilderString;
PredicateBuilderToken(SearchBuilder theSearchBuilder) {
super(theSearchBuilder);
myPredicateBuilderString = theSearchBuilder.getPredicateBuilderString();
}
public Predicate addPredicateToken(String theResourceName,
String theParamName,
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation operation) {
if (theList.get(0).getMissing() != null) {
Join<ResourceTable, ResourceIndexedSearchParamToken> join = createJoin(SearchBuilderJoinEnum.TOKEN, theParamName);
addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
return null;
}
List<Predicate> codePredicates = new ArrayList<>();
List<IQueryParameterType> tokens = new ArrayList<>();
for (IQueryParameterType nextOr : theList) {
if (nextOr instanceof TokenParam) {
TokenParam id = (TokenParam) nextOr;
if (id.isText()) {
myPredicateBuilderString.addPredicateString(theResourceName, theParamName, theList);
break;
}
}
tokens.add(nextOr);
}
if (tokens.isEmpty()) {
return null;
}
Join<ResourceTable, ResourceIndexedSearchParamToken> join = createJoin(SearchBuilderJoinEnum.TOKEN, theParamName);
Collection<Predicate> singleCode = createPredicateToken(tokens, theResourceName, theParamName, myBuilder, join, operation);
assert singleCode != null;
codePredicates.addAll(singleCode);
Predicate spPredicate = myBuilder.or(toArray(codePredicates));
myPredicates.add(spPredicate);
return spPredicate;
}
public Collection<Predicate> createPredicateToken(Collection<IQueryParameterType> theParameters,
String theResourceName,
String theParamName,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamToken> theFrom) {
return createPredicateToken(
theParameters,
theResourceName,
theParamName,
theBuilder,
theFrom,
null);
}
private Collection<Predicate> createPredicateToken(Collection<IQueryParameterType> theParameters,
String theResourceName,
String theParamName,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamToken> theFrom,
SearchFilterParser.CompareOperation operation) {
final List<VersionIndependentConcept> codes = new ArrayList<>();
TokenParamModifier modifier = null;
for (IQueryParameterType nextParameter : theParameters) {
String code;
String system;
if (nextParameter instanceof TokenParam) {
TokenParam id = (TokenParam) nextParameter;
system = id.getSystem();
code = (id.getValue());
modifier = id.getModifier();
} else if (nextParameter instanceof BaseIdentifierDt) {
BaseIdentifierDt id = (BaseIdentifierDt) nextParameter;
system = id.getSystemElement().getValueAsString();
code = (id.getValueElement().getValue());
} else if (nextParameter instanceof BaseCodingDt) {
BaseCodingDt id = (BaseCodingDt) nextParameter;
system = id.getSystemElement().getValueAsString();
code = (id.getCodeElement().getValue());
} else if (nextParameter instanceof NumberParam) {
NumberParam number = (NumberParam) nextParameter;
system = null;
code = number.getValueAsQueryToken(myContext);
} else {
throw new IllegalArgumentException("Invalid token type: " + nextParameter.getClass());
}
if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
throw new InvalidRequestException(
"Parameter[" + theParamName + "] has system (" + system.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + system);
}
if (code != null && code.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
throw new InvalidRequestException(
"Parameter[" + theParamName + "] has code (" + code.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + code);
}
/*
* Process token modifiers (:in, :below, :above)
*/
if (modifier == TokenParamModifier.IN) {
codes.addAll(myTerminologySvc.expandValueSet(code));
} else if (modifier == TokenParamModifier.ABOVE) {
system = determineSystemIfMissing(theParamName, code, system);
validateHaveSystemAndCodeForToken(theParamName, code, system);
codes.addAll(myTerminologySvc.findCodesAbove(system, code));
} else if (modifier == TokenParamModifier.BELOW) {
system = determineSystemIfMissing(theParamName, code, system);
validateHaveSystemAndCodeForToken(theParamName, code, system);
codes.addAll(myTerminologySvc.findCodesBelow(system, code));
} else {
codes.add(new VersionIndependentConcept(system, code));
}
}
List<VersionIndependentConcept> sortedCodesList = codes
.stream()
.filter(t -> t.getCode() != null || t.getSystem() != null)
.sorted()
.distinct()
.collect(Collectors.toList());
if (codes.isEmpty()) {
// This will never match anything
return Collections.singletonList(new BooleanStaticAssertionPredicate((CriteriaBuilderImpl) theBuilder, false));
}
List<Predicate> retVal = new ArrayList<>();
// System only
List<VersionIndependentConcept> systemOnlyCodes = sortedCodesList.stream().filter(t -> isBlank(t.getCode())).collect(Collectors.toList());
if (!systemOnlyCodes.isEmpty()) {
retVal.add(addPredicateToken(theResourceName, theParamName, theBuilder, theFrom, systemOnlyCodes, modifier, SearchBuilderTokenModeEnum.SYSTEM_ONLY));
}
// Code only
List<VersionIndependentConcept> codeOnlyCodes = sortedCodesList.stream().filter(t -> t.getSystem() == null).collect(Collectors.toList());
if (!codeOnlyCodes.isEmpty()) {
retVal.add(addPredicateToken(theResourceName, theParamName, theBuilder, theFrom, codeOnlyCodes, modifier, SearchBuilderTokenModeEnum.VALUE_ONLY));
}
// System and code
List<VersionIndependentConcept> systemAndCodeCodes = sortedCodesList.stream().filter(t -> isNotBlank(t.getCode()) && t.getSystem() != null).collect(Collectors.toList());
if (!systemAndCodeCodes.isEmpty()) {
retVal.add(addPredicateToken(theResourceName, theParamName, theBuilder, theFrom, systemAndCodeCodes, modifier, SearchBuilderTokenModeEnum.SYSTEM_AND_VALUE));
}
return retVal;
}
private String determineSystemIfMissing(String theParamName, String code, String theSystem) {
String retVal = theSystem;
if (retVal == null) {
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceName);
RuntimeSearchParam param = mySearchParamRegistry.getSearchParamByName(resourceDef, theParamName);
if (param != null) {
Set<String> valueSetUris = Sets.newHashSet();
for (String nextPath : param.getPathsSplit()) {
BaseRuntimeChildDefinition def = myContext.newTerser().getDefinition(myResourceType, nextPath);
if (def instanceof BaseRuntimeDeclaredChildDefinition) {
String valueSet = ((BaseRuntimeDeclaredChildDefinition) def).getBindingValueSet();
if (isNotBlank(valueSet)) {
valueSetUris.add(valueSet);
}
}
}
if (valueSetUris.size() == 1) {
String valueSet = valueSetUris.iterator().next();
List<VersionIndependentConcept> candidateCodes = myTerminologySvc.expandValueSet(valueSet);
for (VersionIndependentConcept nextCandidate : candidateCodes) {
if (nextCandidate.getCode().equals(code)) {
retVal = nextCandidate.getSystem();
break;
}
}
}
}
}
return retVal;
}
private void validateHaveSystemAndCodeForToken(String theParamName, String theCode, String theSystem) {
String systemDesc = defaultIfBlank(theSystem, "(missing)");
String codeDesc = defaultIfBlank(theCode, "(missing)");
if (isBlank(theCode)) {
String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, "invalidCodeMissingSystem", theParamName, systemDesc, codeDesc);
throw new InvalidRequestException(msg);
}
if (isBlank(theSystem)) {
String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, "invalidCodeMissingCode", theParamName, systemDesc, codeDesc);
throw new InvalidRequestException(msg);
}
}
private Predicate addPredicateToken(String theResourceName, String theParamName, CriteriaBuilder theBuilder, From<?, ResourceIndexedSearchParamToken> theFrom, List<VersionIndependentConcept> theTokens, TokenParamModifier theModifier, SearchBuilderTokenModeEnum theTokenMode) {
if (myDontUseHashesForSearch) {
final Path<String> systemExpression = theFrom.get("mySystem");
final Path<String> valueExpression = theFrom.get("myValue");
List<Predicate> orPredicates = new ArrayList<>();
switch (theTokenMode) {
case SYSTEM_ONLY: {
List<String> systems = theTokens.stream().map(t -> t.getSystem()).collect(Collectors.toList());
Predicate orPredicate = systemExpression.in(systems);
orPredicates.add(orPredicate);
break;
}
case VALUE_ONLY:
List<String> codes = theTokens.stream().map(t -> t.getCode()).collect(Collectors.toList());
Predicate orPredicate = valueExpression.in(codes);
orPredicates.add(orPredicate);
break;
case SYSTEM_AND_VALUE:
for (VersionIndependentConcept next : theTokens) {
orPredicates.add(theBuilder.and(
toEqualOrIsNullPredicate(systemExpression, next.getSystem()),
toEqualOrIsNullPredicate(valueExpression, next.getCode())
));
}
break;
}
Predicate or = theBuilder.or(orPredicates.toArray(new Predicate[0]));
if (theModifier == TokenParamModifier.NOT) {
or = theBuilder.not(or);
}
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, or);
}
/*
* Note: A null system value means "match any system", but
* an empty-string system value means "match values that
* explicitly have no system".
*/
Expression<Long> hashField;
List<Long> values;
switch (theTokenMode) {
case SYSTEM_ONLY:
hashField = theFrom.get("myHashSystem").as(Long.class);
values = theTokens
.stream()
.map(t -> ResourceIndexedSearchParamToken.calculateHashSystem(theResourceName, theParamName, t.getSystem()))
.collect(Collectors.toList());
break;
case VALUE_ONLY:
hashField = theFrom.get("myHashValue").as(Long.class);
values = theTokens
.stream()
.map(t -> ResourceIndexedSearchParamToken.calculateHashValue(theResourceName, theParamName, t.getCode()))
.collect(Collectors.toList());
break;
case SYSTEM_AND_VALUE:
default:
hashField = theFrom.get("myHashSystemAndValue").as(Long.class);
values = theTokens
.stream()
.map(t -> ResourceIndexedSearchParamToken.calculateHashSystemAndValue(theResourceName, theParamName, t.getSystem(), t.getCode()))
.collect(Collectors.toList());
break;
}
Predicate predicate = hashField.in(values);
if (theModifier == TokenParamModifier.NOT) {
Predicate identityPredicate = theBuilder.equal(theFrom.get("myHashIdentity").as(Long.class), BaseResourceIndexedSearchParam.calculateHashIdentity(theResourceName, theParamName));
Predicate disjunctionPredicate = theBuilder.not(predicate);
predicate = theBuilder.and(identityPredicate, disjunctionPredicate);
}
return predicate;
}
private <T> Expression<Boolean> toEqualOrIsNullPredicate(Path<T> theExpression, T theCode) {
if (theCode == null) {
return myBuilder.isNull(theExpression);
}
return myBuilder.equal(theExpression, theCode);
}
}

View File

@ -0,0 +1,160 @@
package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchFilterParser;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.param.UriParamQualifierEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class PredicateBuilderUri extends BasePredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderUri.class);
@Autowired
private IResourceIndexedSearchParamUriDao myResourceIndexedSearchParamUriDao;
PredicateBuilderUri(SearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}
public Predicate addPredicateUri(String theResourceName,
String theParamName,
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation operation) {
Join<ResourceTable, ResourceIndexedSearchParamUri> join = createJoin(SearchBuilderJoinEnum.URI, theParamName);
if (theList.get(0).getMissing() != null) {
addPredicateParamMissing(theResourceName, theParamName, theList.get(0).getMissing(), join);
return null;
}
List<Predicate> codePredicates = new ArrayList<>();
for (IQueryParameterType nextOr : theList) {
if (nextOr instanceof UriParam) {
UriParam param = (UriParam) nextOr;
String value = param.getValue();
if (value == null) {
continue;
}
if (param.getQualifier() == UriParamQualifierEnum.ABOVE) {
/*
* :above is an inefficient query- It means that the user is supplying a more specific URL (say
* http://example.com/foo/bar/baz) and that we should match on any URLs that are less
* specific but otherwise the same. For example http://example.com and http://example.com/foo would both
* match.
*
* We do this by querying the DB for all candidate URIs and then manually checking each one. This isn't
* very efficient, but this is also probably not a very common type of query to do.
*
* If we ever need to make this more efficient, lucene could certainly be used as an optimization.
*/
ourLog.info("Searching for candidate URI:above parameters for Resource[{}] param[{}]", myResourceName, theParamName);
Collection<String> candidates = myResourceIndexedSearchParamUriDao.findAllByResourceTypeAndParamName(myResourceName, theParamName);
List<String> toFind = new ArrayList<>();
for (String next : candidates) {
if (value.length() >= next.length()) {
if (value.substring(0, next.length()).equals(next)) {
toFind.add(next);
}
}
}
if (toFind.isEmpty()) {
continue;
}
Predicate uriPredicate = join.get("myUri").as(String.class).in(toFind);
Predicate hashAndUriPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, uriPredicate);
codePredicates.add(hashAndUriPredicate);
} else if (param.getQualifier() == UriParamQualifierEnum.BELOW) {
Predicate uriPredicate = myBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value));
Predicate hashAndUriPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, uriPredicate);
codePredicates.add(hashAndUriPredicate);
} else {
if (myDontUseHashesForSearch) {
Predicate predicate = myBuilder.equal(join.get("myUri").as(String.class), value);
codePredicates.add(predicate);
} else {
Predicate uriPredicate = null;
if (operation == null || operation == SearchFilterParser.CompareOperation.eq) {
long hashUri = ResourceIndexedSearchParamUri.calculateHashUri(theResourceName, theParamName, value);
Predicate hashPredicate = myBuilder.equal(join.get("myHashUri"), hashUri);
codePredicates.add(hashPredicate);
} else if (operation == SearchFilterParser.CompareOperation.ne) {
uriPredicate = myBuilder.notEqual(join.get("myUri").as(String.class), value);
} else if (operation == SearchFilterParser.CompareOperation.co) {
uriPredicate = myBuilder.like(join.get("myUri").as(String.class), createLeftAndRightMatchLikeExpression(value));
} else if (operation == SearchFilterParser.CompareOperation.gt) {
uriPredicate = myBuilder.greaterThan(join.get("myUri").as(String.class), value);
} else if (operation == SearchFilterParser.CompareOperation.lt) {
uriPredicate = myBuilder.lessThan(join.get("myUri").as(String.class), value);
} else if (operation == SearchFilterParser.CompareOperation.ge) {
uriPredicate = myBuilder.greaterThanOrEqualTo(join.get("myUri").as(String.class), value);
} else if (operation == SearchFilterParser.CompareOperation.le) {
uriPredicate = myBuilder.lessThanOrEqualTo(join.get("myUri").as(String.class), value);
} else if (operation == SearchFilterParser.CompareOperation.sw) {
uriPredicate = myBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value));
} else if (operation == SearchFilterParser.CompareOperation.ew) {
uriPredicate = myBuilder.like(join.get("myUri").as(String.class), createRightMatchLikeExpression(value));
} else {
throw new IllegalArgumentException(String.format("Unsupported operator specified in _filter clause, %s",
operation.toString()));
}
if (uriPredicate != null) {
long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(theResourceName, theParamName);
Predicate hashIdentityPredicate = myBuilder.equal(join.get("myHashIdentity"), hashIdentity);
codePredicates.add(myBuilder.and(hashIdentityPredicate, uriPredicate));
}
}
}
} else {
throw new IllegalArgumentException("Invalid URI type: " + nextOr.getClass());
}
}
/*
* If we haven't found any of the requested URIs in the candidates, then we'll
* just add a predicate that can never match
*/
if (codePredicates.isEmpty()) {
Predicate predicate = myBuilder.isNull(join.get("myMissing").as(String.class));
myPredicates.add(predicate);
return null;
}
Predicate orPredicate = myBuilder.or(toArray(codePredicates));
Predicate outerPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName,
theParamName,
join,
orPredicate);
myPredicates.add(outerPredicate);
return outerPredicate;
}
}

View File

@ -0,0 +1,13 @@
package ca.uhn.fhir.jpa.dao.predicate;
public enum SearchBuilderJoinEnum {
DATE,
NUMBER,
QUANTITY,
REFERENCE,
STRING,
TOKEN,
URI,
COORDS
}

View File

@ -0,0 +1,35 @@
package ca.uhn.fhir.jpa.dao.predicate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
public class SearchBuilderJoinKey {
private final SearchBuilderJoinEnum myJoinType;
private final String myParamName;
public SearchBuilderJoinKey(String theParamName, SearchBuilderJoinEnum theJoinType) {
super();
myParamName = theParamName;
myJoinType = theJoinType;
}
@Override
public boolean equals(Object theObj) {
if (!(theObj instanceof SearchBuilderJoinKey)) {
return false;
}
SearchBuilderJoinKey obj = (SearchBuilderJoinKey) 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();
}
}

View File

@ -0,0 +1,7 @@
package ca.uhn.fhir.jpa.dao.predicate;
public enum SearchBuilderTokenModeEnum {
SYSTEM_ONLY,
VALUE_ONLY,
SYSTEM_AND_VALUE
}