started to refactor predicates out of SearchBuilder
This commit is contained in:
parent
e6f5af01ed
commit
4c53333539
|
@ -317,7 +317,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
}
|
||||
|
||||
@Override
|
||||
protected DaoConfig getConfig() {
|
||||
public DaoConfig getConfig() {
|
||||
return myConfig;
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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]);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package ca.uhn.fhir.jpa.dao.predicate;
|
||||
|
||||
public enum SearchBuilderJoinEnum {
|
||||
DATE,
|
||||
NUMBER,
|
||||
QUANTITY,
|
||||
REFERENCE,
|
||||
STRING,
|
||||
TOKEN,
|
||||
URI,
|
||||
COORDS
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package ca.uhn.fhir.jpa.dao.predicate;
|
||||
|
||||
public enum SearchBuilderTokenModeEnum {
|
||||
SYSTEM_ONLY,
|
||||
VALUE_ONLY,
|
||||
SYSTEM_AND_VALUE
|
||||
}
|
Loading…
Reference in New Issue