3790 remove legacy search builder (#3791)

* Remove legacy SB

* Add changelog

* Fix more messages

* Fix missed hapi message

* Fix up more messages
This commit is contained in:
Tadgh 2022-07-14 12:01:44 -04:00 committed by GitHub
parent 94a1cda512
commit 05ebf0286d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 95 additions and 12669 deletions

View File

@ -122,19 +122,19 @@ ca.uhn.fhir.jpa.patch.FhirPatch.invalidMoveDestinationIndex=Invalid move destina
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.externalReferenceNotAllowed=Resource contains external reference to URL "{0}" but this server is not configured to allow external references
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.failedToExtractPaths=Failed to extract values from resource using FHIRPath "{0}": {1}
ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl.invalidInclude=Invalid {0} parameter value: "{1}". {2}
ca.uhn.fhir.jpa.dao.LegacySearchBuilder.invalidQuantityPrefix=Unable to handle quantity prefix "{0}" for value: {1}
ca.uhn.fhir.jpa.dao.LegacySearchBuilder.invalidNumberPrefix=Unable to handle number prefix "{0}" for value: {1}
ca.uhn.fhir.jpa.dao.LegacySearchBuilder.sourceParamDisabled=The _source parameter is disabled on this server
ca.uhn.fhir.jpa.dao.LegacySearchBuilder.invalidCodeMissingSystem=Invalid token specified for parameter {0} - No system specified: {1}|{2}
ca.uhn.fhir.jpa.dao.LegacySearchBuilder.invalidCodeMissingCode=Invalid token specified for parameter {0} - No code specified: {1}|{2}
ca.uhn.fhir.jpa.search.builder.predicate.NumberPredicateBuilder.invalidQuantityPrefix=Unable to handle quantity prefix "{0}" for value: {1}
ca.uhn.fhir.jpa.search.builder.predicate.NumberPredicateBuilder.invalidNumberPrefix=Unable to handle number prefix "{0}" for value: {1}
ca.uhn.fhir.jpa.search.builder.QueryStack.sourceParamDisabled=The _source parameter is disabled on this server
ca.uhn.fhir.jpa.search.builder.predicate.TokenPredicateBuilder.invalidCodeMissingSystem=Invalid token specified for parameter {0} - No system specified: {1}|{2}
ca.uhn.fhir.jpa.search.builder.predicate.TokenPredicateBuilder.invalidCodeMissingCode=Invalid token specified for parameter {0} - No code specified: {1}|{2}
ca.uhn.fhir.jpa.term.TermConceptMappingSvcImpl.matchesFound=Matches found
ca.uhn.fhir.jpa.term.TermConceptMappingSvcImpl.noMatchesFound=No Matches found
ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4.invalidSearchParamExpression=The expression "{0}" can not be evaluated and may be invalid: {1}
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderToken.textModifierDisabledForSearchParam=The :text modifier is disabled for this search parameter
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderToken.textModifierDisabledForServer=The :text modifier is disabled on this server
ca.uhn.fhir.jpa.search.builder.QueryStack.textModifierDisabledForSearchParam=The :text modifier is disabled for this search parameter
ca.uhn.fhir.jpa.search.builder.QueryStack.textModifierDisabledForServer=The :text modifier is disabled on this server
ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor.successMsg=Cascaded delete to {0} resources: {1}
ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor.noParam=Note that cascading deletes are not active for this request. You can enable cascading deletes by using the "_cascade=delete" URL parameter.
@ -166,8 +166,8 @@ ca.uhn.fhir.jpa.partition.RequestPartitionHelperSvc.unknownPartitionId=Unknown p
ca.uhn.fhir.jpa.partition.RequestPartitionHelperSvc.unknownPartitionName=Unknown partition name: {0}
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidTargetTypeForChain=Resource type "{0}" is not a valid target type for reference search parameter: {1}
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidResourceType=Invalid/unsupported resource type: "{0}"
ca.uhn.fhir.jpa.search.builder.predicate.ResourceLinkPredicateBuilder.invalidTargetTypeForChain=Resource type "{0}" is not a valid target type for reference search parameter: {1}
ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl.invalidResourceType=Invalid/unsupported resource type: "{0}"
ca.uhn.fhir.jpa.dao.index.IdHelperService.nonUniqueForcedId=Non-unique ID specified, can not process request

View File

@ -0,0 +1,5 @@
---
type: change
issue: 3790
title: "The Legacy Search Builder has been removed in favour of the new Search Builder. The DaoConfig will retain its setter for deprecation purposes,
but that will also be removed after the next release."

View File

@ -0,0 +1,51 @@
When upgrading to this release, there are a few important notes to be aware of:
* This release removes the Legacy Search Builder. If you upgrade to this release, the new Search Builder will automatically be used.
* This release will break existing implementations which use the subscription delete feature. The place where the extension needs to be installed on the Subscription resource has now changed. While it used to be on the top-level Subscription resource, the extension should now be added to the Subscription's `channel` element.
Here is how the subscription should have looked before:
```json
{
"resourceType": "Subscription",
"id": "1",
"status": "active",
"reason": "Monitor resource persistence events",
"criteria": "Patient",
"channel": {
"type": "rest-hook",
"payload": "application/json"
},
"extension": [
{
"url": "http://hapifhir.io/fhir/StructureDefinition/subscription-send-delete-messages",
"valueBoolean": "true"
}
]
}
```
And here is how it should now look:
```json
{
"resourceType": "Subscription",
"id": "1",
"status": "active",
"reason": "Monitor resource persistence events",
"criteria": "Patient",
"channel": {
"extension": [
{
"url": "http://hapifhir.io/fhir/StructureDefinition/subscription-send-delete-messages",
"valueBoolean": "true"
}
],
"type": "rest-hook",
"payload": "application/json"
}
}
```

View File

@ -29,7 +29,6 @@ import ca.uhn.fhir.jpa.dao.HistoryBuilder;
import ca.uhn.fhir.jpa.dao.HistoryBuilderFactory;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.dao.MatchResourceUrlService;
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
import ca.uhn.fhir.jpa.dao.TransactionProcessor;
@ -46,18 +45,6 @@ import ca.uhn.fhir.jpa.dao.index.IJpaIdHelperService;
import ca.uhn.fhir.jpa.dao.index.JpaIdHelperService;
import ca.uhn.fhir.jpa.dao.index.SearchParamWithInlineReferencesExtractor;
import ca.uhn.fhir.jpa.dao.mdm.MdmLinkExpandSvc;
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilder;
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderCoords;
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderDate;
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderFactory;
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderNumber;
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderQuantity;
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference;
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderResourceId;
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderString;
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderTag;
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderToken;
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderUri;
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
import ca.uhn.fhir.jpa.delete.DeleteConflictFinderService;
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
@ -627,65 +614,6 @@ public class JpaConfig {
return new UriPredicateBuilder(theSearchBuilder);
}
@Bean
@Scope("prototype")
public PredicateBuilderCoords newPredicateBuilderCoords(LegacySearchBuilder theSearchBuilder) {
return new PredicateBuilderCoords(theSearchBuilder);
}
@Bean
@Scope("prototype")
public PredicateBuilderDate newPredicateBuilderDate(LegacySearchBuilder theSearchBuilder) {
return new PredicateBuilderDate(theSearchBuilder);
}
@Bean
@Scope("prototype")
public PredicateBuilderNumber newPredicateBuilderNumber(LegacySearchBuilder theSearchBuilder) {
return new PredicateBuilderNumber(theSearchBuilder);
}
@Bean
@Scope("prototype")
public PredicateBuilderQuantity newPredicateBuilderQuantity(LegacySearchBuilder theSearchBuilder) {
return new PredicateBuilderQuantity(theSearchBuilder);
}
@Bean
@Scope("prototype")
public PredicateBuilderReference newPredicateBuilderReference(LegacySearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) {
return new PredicateBuilderReference(theSearchBuilder, thePredicateBuilder);
}
@Bean
@Scope("prototype")
public PredicateBuilderResourceId newPredicateBuilderResourceId(LegacySearchBuilder theSearchBuilder) {
return new PredicateBuilderResourceId(theSearchBuilder);
}
@Bean
@Scope("prototype")
public PredicateBuilderString newPredicateBuilderString(LegacySearchBuilder theSearchBuilder) {
return new PredicateBuilderString(theSearchBuilder);
}
@Bean
@Scope("prototype")
public PredicateBuilderTag newPredicateBuilderTag(LegacySearchBuilder theSearchBuilder) {
return new PredicateBuilderTag(theSearchBuilder);
}
@Bean
@Scope("prototype")
public PredicateBuilderToken newPredicateBuilderToken(LegacySearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) {
return new PredicateBuilderToken(theSearchBuilder, thePredicateBuilder);
}
@Bean
@Scope("prototype")
public PredicateBuilderUri newPredicateBuilderUri(LegacySearchBuilder theSearchBuilder) {
return new PredicateBuilderUri(theSearchBuilder);
}
@Bean
@Scope("prototype")
@ -696,9 +624,6 @@ public class JpaConfig {
@Bean(name = SEARCH_BUILDER)
@Scope("prototype")
public ISearchBuilder newSearchBuilder(IDao theDao, String theResourceName, Class<? extends IBaseResource> theResourceType, DaoConfig theDaoConfig) {
if (theDaoConfig.isUseLegacySearchBuilder()) {
return new LegacySearchBuilder(theDao, theResourceName, theResourceType);
}
return new SearchBuilder(theDao, theResourceName, theResourceType);
}
@ -790,11 +715,6 @@ public class JpaConfig {
return new CacheWarmingSvcImpl();
}
@Bean
public PredicateBuilderFactory predicateBuilderFactory(ApplicationContext theApplicationContext) {
return new PredicateBuilderFactory(theApplicationContext);
}
@Bean
public IndexNamePrefixLayoutStrategy indexLayoutStrategy() {
return new IndexNamePrefixLayoutStrategy();

View File

@ -50,7 +50,8 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import static ca.uhn.fhir.jpa.dao.LegacySearchBuilder.toPredicateArray;
import static ca.uhn.fhir.jpa.search.builder.SearchBuilder.toPredicateArray;
/**
* The HistoryBuilder is responsible for building history queries

View File

@ -1,213 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.dao.predicate.querystack.QueryStack;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.BasePartitionable;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresentEntity;
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.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Predicate;
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);
final CriteriaBuilder myCriteriaBuilder;
final QueryStack myQueryStack;
final Class<? extends IBaseResource> myResourceType;
final String myResourceName;
final SearchParameterMap myParams;
@Autowired
FhirContext myContext;
@Autowired
DaoConfig myDaoConfig;
boolean myDontUseHashesForSearch;
@Autowired
private PartitionSettings myPartitionSettings;
BasePredicateBuilder(LegacySearchBuilder theSearchBuilder) {
myCriteriaBuilder = theSearchBuilder.getBuilder();
myQueryStack = theSearchBuilder.getQueryStack();
myResourceType = theSearchBuilder.getResourceType();
myResourceName = theSearchBuilder.getResourceName();
myParams = theSearchBuilder.getParams();
}
@PostConstruct
private void postConstruct() {
myDontUseHashesForSearch = myDaoConfig.getDisableHashBasedSearches();
}
void addPredicateParamMissingForReference(String theResourceName, String theParamName, boolean theMissing, RequestPartitionId theRequestPartitionId) {
From<?, SearchParamPresentEntity> paramPresentJoin = myQueryStack.createJoin(SearchBuilderJoinEnum.PRESENCE, null);
Expression<Long> hashPresence = paramPresentJoin.get("myHashPresence").as(Long.class);
Long hash = SearchParamPresentEntity.calculateHashPresence(myPartitionSettings, theRequestPartitionId, theResourceName, theParamName, !theMissing);
List<Predicate> predicates = new ArrayList<>();
predicates.add(myCriteriaBuilder.equal(hashPresence, hash));
addPartitionIdPredicate(theRequestPartitionId, paramPresentJoin, predicates);
myQueryStack.addPredicatesWithImplicitTypeSelection(predicates);
}
void addPredicateParamMissingForNonReference(String theResourceName, String theParamName, boolean theMissing, From<?, ? extends BaseResourceIndexedSearchParam> theJoin, RequestPartitionId theRequestPartitionId) {
if (!theRequestPartitionId.isAllPartitions()) {
if (theRequestPartitionId.isDefaultPartition()) {
myQueryStack.addPredicate(myCriteriaBuilder.isNull(theJoin.get("myPartitionIdValue")));
} else {
myQueryStack.addPredicate(theJoin.get("myPartitionIdValue").in(theRequestPartitionId.getPartitionIds()));
}
}
myQueryStack.addPredicateWithImplicitTypeSelection(myCriteriaBuilder.equal(theJoin.get("myResourceType"), theResourceName));
myQueryStack.addPredicate(myCriteriaBuilder.equal(theJoin.get("myParamName"), theParamName));
myQueryStack.addPredicate(myCriteriaBuilder.equal(theJoin.get("myMissing"), theMissing));
}
Predicate combineParamIndexPredicateWithParamNamePredicate(String theResourceName, String theParamName, From<?, ? extends BaseResourceIndexedSearchParam> theFrom, Predicate thePredicate, RequestPartitionId theRequestPartitionId) {
List<Predicate> andPredicates = new ArrayList<>();
addPartitionIdPredicate(theRequestPartitionId, theFrom, andPredicates);
if (myDontUseHashesForSearch) {
Predicate resourceTypePredicate = myCriteriaBuilder.equal(theFrom.get("myResourceType"), theResourceName);
Predicate paramNamePredicate = myCriteriaBuilder.equal(theFrom.get("myParamName"), theParamName);
andPredicates.add(resourceTypePredicate);
andPredicates.add(paramNamePredicate);
andPredicates.add(thePredicate);
} else {
long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(myPartitionSettings, theRequestPartitionId, theResourceName, theParamName);
Predicate hashIdentityPredicate = myCriteriaBuilder.equal(theFrom.get("myHashIdentity"), hashIdentity);
andPredicates.add(hashIdentityPredicate);
andPredicates.add(thePredicate);
}
return myCriteriaBuilder.and(toArray(andPredicates));
}
public PartitionSettings getPartitionSettings() {
return myPartitionSettings;
}
Predicate createPredicateNumeric(String theResourceName,
String theParamName,
From<?, ? extends BaseResourceIndexedSearchParam> theFrom,
CriteriaBuilder builder,
IQueryParameterType theParam,
ParamPrefixEnum thePrefix,
BigDecimal theValue,
final Expression<BigDecimal> thePath,
String invalidMessageName, RequestPartitionId theRequestPartitionId) {
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 = SearchFuzzUtil.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(LegacySearchBuilder.class, invalidMessageName, thePrefix.getValue(), theParam.getValueAsQueryToken(myContext));
throw new InvalidRequestException(Msg.code(1069) + msg);
}
if (theParamName == null) {
return num;
}
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, num, theRequestPartitionId);
}
void addPartitionIdPredicate(RequestPartitionId theRequestPartitionId, From<?, ? extends BasePartitionable> theJoin, List<Predicate> theCodePredicates) {
if (!theRequestPartitionId.isAllPartitions()) {
Predicate partitionPredicate;
if (theRequestPartitionId.isDefaultPartition()) {
partitionPredicate = myCriteriaBuilder.isNull(theJoin.get("myPartitionIdValue").as(Integer.class));
} else {
partitionPredicate = theJoin.get("myPartitionIdValue").as(Integer.class).in(theRequestPartitionId.getPartitionIds());
}
myQueryStack.addPredicate(partitionPredicate);
}
}
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

@ -1,38 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.model.api.IQueryParameterType;
import javax.annotation.Nullable;
import javax.persistence.criteria.Predicate;
import java.util.List;
public interface IPredicateBuilder {
@Nullable
Predicate addPredicate(String theResourceName,
RuntimeSearchParam theSearchParam,
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation operation,
RequestPartitionId theRequestPartitionId);
}

View File

@ -1,38 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
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<?, ?> theJoin) {
myIndexJoins.put(theKey, theJoin);
}
public Join<?,?> get(SearchBuilderJoinKey theKey) {
return myIndexJoins.get(theKey);
}
}

View File

@ -1,139 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Subquery;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class PredicateBuilder {
private final PredicateBuilderCoords myPredicateBuilderCoords;
private final PredicateBuilderDate myPredicateBuilderDate;
private final PredicateBuilderNumber myPredicateBuilderNumber;
private final PredicateBuilderQuantity myPredicateBuilderQuantity;
private final PredicateBuilderReference myPredicateBuilderReference;
private final PredicateBuilderResourceId myPredicateBuilderResourceId;
private final PredicateBuilderString myPredicateBuilderString;
private final PredicateBuilderTag myPredicateBuilderTag;
private final PredicateBuilderToken myPredicateBuilderToken;
private final PredicateBuilderUri myPredicateBuilderUri;
public PredicateBuilder(LegacySearchBuilder theSearchBuilder, PredicateBuilderFactory thePredicateBuilderFactory) {
myPredicateBuilderCoords = thePredicateBuilderFactory.newPredicateBuilderCoords(theSearchBuilder);
myPredicateBuilderDate = thePredicateBuilderFactory.newPredicateBuilderDate(theSearchBuilder);
myPredicateBuilderNumber = thePredicateBuilderFactory.newPredicateBuilderNumber(theSearchBuilder);
myPredicateBuilderQuantity = thePredicateBuilderFactory.newPredicateBuilderQuantity(theSearchBuilder);
myPredicateBuilderReference = thePredicateBuilderFactory.newPredicateBuilderReference(theSearchBuilder, this);
myPredicateBuilderResourceId = thePredicateBuilderFactory.newPredicateBuilderResourceId(theSearchBuilder);
myPredicateBuilderString = thePredicateBuilderFactory.newPredicateBuilderString(theSearchBuilder);
myPredicateBuilderTag = thePredicateBuilderFactory.newPredicateBuilderTag(theSearchBuilder);
myPredicateBuilderToken = thePredicateBuilderFactory.newPredicateBuilderToken(theSearchBuilder, this);
myPredicateBuilderUri = thePredicateBuilderFactory.newPredicateBuilderUri(theSearchBuilder);
}
void addPredicateCoords(String theResourceName, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theNextAnd, RequestPartitionId theRequestPartitionId) {
myPredicateBuilderCoords.addPredicate(theResourceName, theSearchParam, theNextAnd, null, theRequestPartitionId);
}
Predicate addPredicateDate(String theResourceName, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
return myPredicateBuilderDate.addPredicate(theResourceName, theSearchParam, theNextAnd, theOperation, theRequestPartitionId);
}
Predicate addPredicateNumber(String theResourceName, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
return myPredicateBuilderNumber.addPredicate(theResourceName, theSearchParam, theNextAnd, theOperation, theRequestPartitionId);
}
Predicate addPredicateQuantity(String theResourceName, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
return myPredicateBuilderQuantity.addPredicate(theResourceName, theSearchParam, theNextAnd, theOperation, theRequestPartitionId);
}
void addPredicateString(String theResourceName, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theNextAnd, RequestPartitionId theRequestPartitionId) {
myPredicateBuilderString.addPredicate(theResourceName, theSearchParam, theNextAnd, SearchFilterParser.CompareOperation.sw, theRequestPartitionId);
}
Predicate addPredicateString(String theResourceName,RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
return myPredicateBuilderString.addPredicate(theResourceName, theSearchParam, theNextAnd, theOperation, theRequestPartitionId);
}
void addPredicateTag(List<List<IQueryParameterType>> theAndOrParams, String theParamName, RequestPartitionId theRequestPartitionId) {
myPredicateBuilderTag.addPredicateTag(theAndOrParams, theParamName, theRequestPartitionId);
}
Predicate addPredicateToken(String theResourceName, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
return myPredicateBuilderToken.addPredicate(theResourceName, theSearchParam, theNextAnd, theOperation, theRequestPartitionId);
}
Predicate addPredicateUri(String theResourceName, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theSingletonList, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
return myPredicateBuilderUri.addPredicate(theResourceName, theSearchParam, theSingletonList, theOperation, theRequestPartitionId);
}
public void searchForIdsWithAndOr(String theResourceName, String theNextParamName, List<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
myPredicateBuilderReference.searchForIdsWithAndOr(theResourceName, theNextParamName, theAndOrParams, theRequest, theRequestPartitionId);
}
Subquery<Long> createLinkSubquery(String theParameterName, String theTargetResourceType, ArrayList<IQueryParameterType> theOrValues, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
return myPredicateBuilderReference.createLinkSubquery(theParameterName, theTargetResourceType, theOrValues, theRequest, theRequestPartitionId);
}
Predicate createResourceLinkPathPredicate(String theTargetResourceType, String theParamReference, Join<?, ResourceLink> theJoin) {
return myPredicateBuilderReference.createResourceLinkPathPredicate(theTargetResourceType, theParamReference, theJoin);
}
void addPredicateResourceId(List<List<IQueryParameterType>> theAndOrParams, String theResourceName, RequestPartitionId theRequestPartitionId) {
myPredicateBuilderResourceId.addPredicateResourceId(theAndOrParams, theResourceName, null, theRequestPartitionId);
}
public Predicate addPredicateResourceId(List<List<IQueryParameterType>> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
return myPredicateBuilderResourceId.addPredicateResourceId(theValues, theResourceName, theOperation, theRequestPartitionId);
}
Predicate createPredicateString(IQueryParameterType theLeftValue, String theResourceName, RuntimeSearchParam theSearchParam, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> theStringJoin, RequestPartitionId theRequestPartitionId) {
return myPredicateBuilderString.createPredicateString(theLeftValue, theResourceName, theSearchParam, theBuilder, theStringJoin, theRequestPartitionId);
}
Collection<Predicate> createPredicateToken(List<IQueryParameterType> theTokens, String theResourceName, RuntimeSearchParam theSearchParam, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamToken, ResourceIndexedSearchParamToken> theTokenJoin, RequestPartitionId theRequestPartitionId) {
return myPredicateBuilderToken.createPredicateToken(theTokens, theResourceName, theSearchParam, theBuilder, theTokenJoin, theRequestPartitionId);
}
Predicate createPredicateDate(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamDate, ResourceIndexedSearchParamDate> theDateJoin, RequestPartitionId theRequestPartitionId) {
return myPredicateBuilderDate.createPredicateDate(theLeftValue, theResourceName, theName, theBuilder, theDateJoin, theRequestPartitionId);
}
Predicate createPredicateQuantity(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From<ResourceIndexedSearchParamQuantity, ResourceIndexedSearchParamQuantity> theDateJoin, RequestPartitionId theRequestPartitionId) {
return myPredicateBuilderQuantity.createPredicateQuantity(theLeftValue, theResourceName, theName, theBuilder, theDateJoin, theRequestPartitionId);
}
}

View File

@ -1,179 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
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 com.google.common.annotations.VisibleForTesting;
import org.hibernate.search.engine.spatial.GeoBoundingBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.isBlank;
@Component
@Scope("prototype")
public class PredicateBuilderCoords extends BasePredicateBuilder implements IPredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderCoords.class);
public PredicateBuilderCoords(LegacySearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}
private Predicate createPredicateCoords(IQueryParameterType theParam,
String theResourceName,
RuntimeSearchParam theSearchParam,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamCoords> theFrom,
RequestPartitionId theRequestPartitionId) {
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(Msg.code(1038) + "Invalid position format '" + value + "'. Required format is 'latitude:longitude'");
}
latitudeValue = parts[0];
longitudeValue = parts[1];
if (isBlank(latitudeValue) || isBlank(longitudeValue)) {
throw new IllegalArgumentException(Msg.code(1039) + "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(Msg.code(1040) + "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(Msg.code(1041) + "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(Msg.code(1042) + "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(Msg.code(1043) + "Invalid " + Location.SP_NEAR_DISTANCE + " parameter '" + distanceKm + "' must be >= 0.0");
} else if (distanceKm > CoordCalculator.MAX_SUPPORTED_DISTANCE_KM) {
throw new IllegalArgumentException(Msg.code(1044) + "Invalid " + Location.SP_NEAR_DISTANCE + " parameter '" + distanceKm + "' must be <= " + CoordCalculator.MAX_SUPPORTED_DISTANCE_KM);
} else {
double latitudeDegrees = Double.parseDouble(latitudeValue);
double longitudeDegrees = Double.parseDouble(longitudeValue);
GeoBoundingBox box = CoordCalculator.getBox(latitudeDegrees, longitudeDegrees, distanceKm);
latitudePredicate = latitudePredicateFromBox(theBuilder, theFrom, box);
longitudePredicate = longitudePredicateFromBox(theBuilder, theFrom, box);
}
Predicate singleCode = theBuilder.and(latitudePredicate, longitudePredicate);
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theSearchParam.getName(), theFrom, singleCode, theRequestPartitionId);
}
private Predicate latitudePredicateFromBox(CriteriaBuilder theBuilder, From<?, ResourceIndexedSearchParamCoords> theFrom, GeoBoundingBox theBox) {
return theBuilder.and(
theBuilder.greaterThanOrEqualTo(theFrom.get("myLatitude"), theBox.bottomRight().latitude()),
theBuilder.lessThanOrEqualTo(theFrom.get("myLatitude"), theBox.topLeft().latitude())
);
}
@VisibleForTesting
Predicate longitudePredicateFromBox(CriteriaBuilder theBuilder, From<?, ResourceIndexedSearchParamCoords> theFrom, GeoBoundingBox theBox) {
if (theBox.bottomRight().longitude() < theBox.topLeft().longitude()) {
return theBuilder.or(
theBuilder.greaterThanOrEqualTo(theFrom.get("myLongitude"), theBox.bottomRight().longitude()),
theBuilder.lessThanOrEqualTo(theFrom.get("myLongitude"), theBox.topLeft().longitude())
);
}
return theBuilder.and(
theBuilder.greaterThanOrEqualTo(theFrom.get("myLongitude"), theBox.topLeft().longitude()),
theBuilder.lessThanOrEqualTo(theFrom.get("myLongitude"), theBox.bottomRight().longitude())
);
}
@Override
public Predicate addPredicate(String theResourceName,
RuntimeSearchParam theSearchParam,
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation theOperation,
RequestPartitionId theRequestPartitionId) {
From<?, ResourceIndexedSearchParamCoords> join = myQueryStack.createJoin(SearchBuilderJoinEnum.COORDS, theSearchParam.getName());
if (theList.get(0).getMissing() != null) {
addPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), join, theRequestPartitionId);
return null;
}
List<Predicate> codePredicates = new ArrayList<>();
addPartitionIdPredicate(theRequestPartitionId, join, codePredicates);
for (IQueryParameterType nextOr : theList) {
Predicate singleCode = createPredicateCoords(nextOr,
theResourceName,
theSearchParam,
myCriteriaBuilder,
join,
theRequestPartitionId);
codePredicates.add(singleCode);
}
Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates));
myQueryStack.addPredicateWithImplicitTypeSelection(retVal);
return retVal;
}
}

View File

@ -1,317 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
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 org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Component
@Scope("prototype")
public class PredicateBuilderDate extends BasePredicateBuilder implements IPredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderDate.class);
public PredicateBuilderDate(LegacySearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}
@Override
public Predicate addPredicate(String theResourceName,
RuntimeSearchParam theSearchParam,
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation operation,
RequestPartitionId theRequestPartitionId) {
String paramName = theSearchParam.getName();
boolean newJoin = false;
Map<String, From<?, ResourceIndexedSearchParamDate>> joinMap = myQueryStack.getJoinMap();
String key = theResourceName + " " + paramName;
From<?, ResourceIndexedSearchParamDate> join = joinMap.get(key);
if (join == null) {
join = myQueryStack.createJoin(SearchBuilderJoinEnum.DATE, paramName);
joinMap.put(key, join);
newJoin = true;
}
if (theList.get(0).getMissing() != null) {
Boolean missing = theList.get(0).getMissing();
addPredicateParamMissingForNonReference(theResourceName, paramName, missing, join, theRequestPartitionId);
return null;
}
List<Predicate> codePredicates = new ArrayList<>();
for (IQueryParameterType nextOr : theList) {
Predicate p = createPredicateDate(nextOr,
myCriteriaBuilder,
join,
operation
);
codePredicates.add(p);
}
Predicate orPredicates = myCriteriaBuilder.or(toArray(codePredicates));
if (newJoin) {
Predicate identityAndValuePredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, join, orPredicates, theRequestPartitionId);
myQueryStack.addPredicateWithImplicitTypeSelection(identityAndValuePredicate);
} else {
myQueryStack.addPredicateWithImplicitTypeSelection(orPredicates);
}
return orPredicates;
}
public Predicate createPredicateDate(IQueryParameterType theParam,
String theResourceName,
String theParamName,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamDate> theFrom,
RequestPartitionId theRequestPartitionId) {
Predicate predicateDate = createPredicateDate(theParam,
theBuilder,
theFrom,
null
);
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, predicateDate, theRequestPartitionId);
}
private Predicate createPredicateDate(IQueryParameterType theParam,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamDate> theFrom,
SearchFilterParser.CompareOperation theOperation) {
Predicate p;
if (theParam instanceof DateParam) {
DateParam date = (DateParam) theParam;
if (!date.isEmpty()) {
if (theOperation == SearchFilterParser.CompareOperation.ne) {
date = new DateParam(ParamPrefixEnum.EQUAL, date.getValueAsString());
}
DateRangeParam range = new DateRangeParam(date);
p = createPredicateDateFromRange(theBuilder,
theFrom,
range,
theOperation);
} else {
// TODO: handle missing date param?
p = null;
}
} else if (theParam instanceof DateRangeParam) {
DateRangeParam range = (DateRangeParam) theParam;
p = createPredicateDateFromRange(theBuilder,
theFrom,
range,
theOperation);
} else {
throw new IllegalArgumentException(Msg.code(1001) + "Invalid token type: " + theParam.getClass());
}
return p;
}
private boolean isNullOrDayPrecision(DateParam theDateParam) {
return theDateParam == null || theDateParam.getPrecision().ordinal() == TemporalPrecisionEnum.DAY.ordinal();
}
@SuppressWarnings("unchecked")
private Predicate createPredicateDateFromRange(CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamDate> theFrom,
DateRangeParam theRange,
SearchFilterParser.CompareOperation operation) {
Date lowerBoundInstant = theRange.getLowerBoundAsInstant();
Date upperBoundInstant = theRange.getUpperBoundAsInstant();
DateParam lowerBound = theRange.getLowerBound();
DateParam upperBound = theRange.getUpperBound();
Integer lowerBoundAsOrdinal = theRange.getLowerBoundAsDateInteger();
Integer upperBoundAsOrdinal = theRange.getUpperBoundAsDateInteger();
Comparable genericLowerBound;
Comparable genericUpperBound;
/**
* If all present search parameters are of DAY precision, and {@link DaoConfig#getUseOrdinalDatesForDayPrecisionSearches()} is true,
* then we attempt to use the ordinal field for date comparisons instead of the date field.
*/
boolean isOrdinalComparison = isNullOrDayPrecision(lowerBound) && isNullOrDayPrecision(upperBound) && myDaoConfig.getModelConfig().getUseOrdinalDatesForDayPrecisionSearches();
Predicate lt = null;
Predicate gt = null;
Predicate lb = null;
Predicate ub = null;
String lowValueField;
String highValueField;
if (isOrdinalComparison) {
lowValueField = "myValueLowDateOrdinal";
highValueField = "myValueHighDateOrdinal";
genericLowerBound = lowerBoundAsOrdinal;
genericUpperBound = upperBoundAsOrdinal;
} else {
lowValueField = "myValueLow";
highValueField = "myValueHigh";
genericLowerBound = lowerBoundInstant;
genericUpperBound = upperBoundInstant;
}
if (operation == SearchFilterParser.CompareOperation.lt) {
// use lower bound first
if (lowerBoundInstant != null) {
// the value has been reduced one in this case
lb = theBuilder.lessThanOrEqualTo(theFrom.get(lowValueField), genericLowerBound);
if (myDaoConfig.isAccountForDateIndexNulls()) {
lb = theBuilder.or(lb, theBuilder.lessThanOrEqualTo(theFrom.get(highValueField), genericLowerBound));
}
} else {
if (upperBoundInstant != null) {
ub = theBuilder.lessThanOrEqualTo(theFrom.get(lowValueField), genericUpperBound);
if (myDaoConfig.isAccountForDateIndexNulls()) {
ub = theBuilder.or(ub, theBuilder.lessThanOrEqualTo(theFrom.get(highValueField), genericUpperBound));
}
} else {
throw new InvalidRequestException(Msg.code(1002) + "lowerBound and upperBound value not correctly specified for compare theOperation");
}
}
} else if (operation == SearchFilterParser.CompareOperation.le) {
// use lower bound first
if (lowerBoundInstant != null) {
lb = theBuilder.lessThanOrEqualTo(theFrom.get(lowValueField), genericLowerBound);
if (myDaoConfig.isAccountForDateIndexNulls()) {
lb = theBuilder.or(lb, theBuilder.lessThanOrEqualTo(theFrom.get(highValueField), genericLowerBound));
}
} else {
if (upperBoundInstant != null) {
ub = theBuilder.lessThanOrEqualTo(theFrom.get(lowValueField), genericUpperBound);
if (myDaoConfig.isAccountForDateIndexNulls()) {
ub = theBuilder.or(ub, theBuilder.lessThanOrEqualTo(theFrom.get(highValueField), genericUpperBound));
}
} else {
throw new InvalidRequestException(Msg.code(1003) + "lowerBound and upperBound value not correctly specified for compare theOperation");
}
}
} else if (operation == SearchFilterParser.CompareOperation.gt) {
// use upper bound first, e.g value between 6 and 10
// gt7 true, 10>7, gt11 false, 10>11 false, gt5 true, 10>5
if (upperBoundInstant != null) {
ub = theBuilder.greaterThanOrEqualTo(theFrom.get(highValueField), genericUpperBound);
if (myDaoConfig.isAccountForDateIndexNulls()) {
ub = theBuilder.or(ub, theBuilder.greaterThanOrEqualTo(theFrom.get(lowValueField), genericUpperBound));
}
} else {
if (lowerBoundInstant != null) {
lb = theBuilder.greaterThanOrEqualTo(theFrom.get(highValueField), genericLowerBound);
if (myDaoConfig.isAccountForDateIndexNulls()) {
lb = theBuilder.or(lb, theBuilder.greaterThanOrEqualTo(theFrom.get(lowValueField), genericLowerBound));
}
} else {
throw new InvalidRequestException(Msg.code(1004) + "upperBound and lowerBound value not correctly specified for compare theOperation");
}
}
} else if (operation == SearchFilterParser.CompareOperation.ge) {
// use upper bound first, e.g value between 6 and 10
// gt7 true, 10>7, gt11 false, 10>11 false, gt5 true, 10>5
if (upperBoundInstant != null) {
ub = theBuilder.greaterThanOrEqualTo(theFrom.get(highValueField), genericUpperBound);
if (myDaoConfig.isAccountForDateIndexNulls()) {
ub = theBuilder.or(ub, theBuilder.greaterThanOrEqualTo(theFrom.get(lowValueField), genericUpperBound));
}
} else {
if (lowerBoundInstant != null) {
lb = theBuilder.greaterThanOrEqualTo(theFrom.get(highValueField), genericLowerBound);
if (myDaoConfig.isAccountForDateIndexNulls()) {
lb = theBuilder.or(lb, theBuilder.greaterThanOrEqualTo(theFrom.get(lowValueField), genericLowerBound));
}
} else {
throw new InvalidRequestException(Msg.code(1005) + "upperBound and lowerBound value not correctly specified for compare theOperation");
}
}
} else if (operation == SearchFilterParser.CompareOperation.ne) {
if ((lowerBoundInstant == null) ||
(upperBoundInstant == null)) {
throw new InvalidRequestException(Msg.code(1006) + "lowerBound and/or upperBound value not correctly specified for compare operation");
}
lt = theBuilder.lessThan(theFrom.get(lowValueField), genericLowerBound);
gt = theBuilder.greaterThan(theFrom.get(highValueField), genericUpperBound);
lb = theBuilder.or(lt, gt);
} else if ((operation == SearchFilterParser.CompareOperation.eq) || (operation == null)) {
if (lowerBoundInstant != null) {
gt = theBuilder.greaterThanOrEqualTo(theFrom.get(lowValueField), genericLowerBound);
lt = theBuilder.greaterThanOrEqualTo(theFrom.get(highValueField), genericLowerBound);
if (lowerBound.getPrefix() == ParamPrefixEnum.STARTS_AFTER || lowerBound.getPrefix() == ParamPrefixEnum.EQUAL) {
lb = gt;
} else {
lb = theBuilder.or(gt, lt);
}
}
if (upperBoundInstant != null) {
gt = theBuilder.lessThanOrEqualTo(theFrom.get(lowValueField), genericUpperBound);
lt = theBuilder.lessThanOrEqualTo(theFrom.get(highValueField), genericUpperBound);
if (theRange.getUpperBound().getPrefix() == ParamPrefixEnum.ENDS_BEFORE || theRange.getUpperBound().getPrefix() == ParamPrefixEnum.EQUAL) {
ub = lt;
} else {
ub = theBuilder.or(gt, lt);
}
}
} else {
throw new InvalidRequestException(Msg.code(1007) + String.format("Unsupported operator specified, operator=%s",
operation.name()));
}
if (isOrdinalComparison) {
ourLog.trace("Ordinal date range is {} - {} ", lowerBoundAsOrdinal, upperBoundAsOrdinal);
} else {
ourLog.trace("Date range is {} - {}", lowerBoundInstant, upperBoundInstant);
}
if (lb != null && ub != null) {
return (theBuilder.and(lb, ub));
} else if (lb != null) {
return (lb);
} else {
return (ub);
}
}
}

View File

@ -1,78 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
@Service
public class PredicateBuilderFactory {
private final ApplicationContext myApplicationContext;
@Autowired
public PredicateBuilderFactory(ApplicationContext theApplicationContext) {
myApplicationContext = theApplicationContext;
}
public PredicateBuilderCoords newPredicateBuilderCoords(LegacySearchBuilder theSearchBuilder) {
return myApplicationContext.getBean(PredicateBuilderCoords.class, theSearchBuilder);
}
public PredicateBuilderDate newPredicateBuilderDate(LegacySearchBuilder theSearchBuilder) {
return myApplicationContext.getBean(PredicateBuilderDate.class, theSearchBuilder);
}
public PredicateBuilderNumber newPredicateBuilderNumber(LegacySearchBuilder theSearchBuilder) {
return myApplicationContext.getBean(PredicateBuilderNumber.class, theSearchBuilder);
}
public PredicateBuilderQuantity newPredicateBuilderQuantity(LegacySearchBuilder theSearchBuilder) {
return myApplicationContext.getBean(PredicateBuilderQuantity.class, theSearchBuilder);
}
public PredicateBuilderReference newPredicateBuilderReference(LegacySearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) {
return myApplicationContext.getBean(PredicateBuilderReference.class, theSearchBuilder, thePredicateBuilder);
}
public PredicateBuilderResourceId newPredicateBuilderResourceId(LegacySearchBuilder theSearchBuilder) {
return myApplicationContext.getBean(PredicateBuilderResourceId.class, theSearchBuilder);
}
public PredicateBuilderString newPredicateBuilderString(LegacySearchBuilder theSearchBuilder) {
return myApplicationContext.getBean(PredicateBuilderString.class, theSearchBuilder);
}
public PredicateBuilderTag newPredicateBuilderTag(LegacySearchBuilder theSearchBuilder) {
return myApplicationContext.getBean(PredicateBuilderTag.class, theSearchBuilder);
}
public PredicateBuilderToken newPredicateBuilderToken(LegacySearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) {
return myApplicationContext.getBean(PredicateBuilderToken.class, theSearchBuilder, thePredicateBuilder);
}
public PredicateBuilderUri newPredicateBuilderUri(LegacySearchBuilder theSearchBuilder) {
return myApplicationContext.getBean(PredicateBuilderUri.class, theSearchBuilder);
}
}

View File

@ -1,116 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
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 org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Predicate;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
@Component
@Scope("prototype")
public class PredicateBuilderNumber extends BasePredicateBuilder implements IPredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderNumber.class);
public PredicateBuilderNumber(LegacySearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}
@Override
public Predicate addPredicate(String theResourceName,
RuntimeSearchParam theSearchParam,
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation operation,
RequestPartitionId theRequestPartitionId) {
From<?, ResourceIndexedSearchParamNumber> join = myQueryStack.createJoin(SearchBuilderJoinEnum.NUMBER, theSearchParam.getName());
if (theList.get(0).getMissing() != null) {
addPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), join, theRequestPartitionId);
return null;
}
List<Predicate> codePredicates = new ArrayList<>();
addPartitionIdPredicate(theRequestPartitionId, join, codePredicates);
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(Msg.code(999) + "Invalid operator specified for number type");
}
String invalidMessageName = "invalidNumberPrefix";
Predicate predicateNumeric = createPredicateNumeric(theResourceName, theSearchParam.getName(), join, myCriteriaBuilder, nextOr, prefix, value, fromObj, invalidMessageName, theRequestPartitionId);
Predicate predicateOuter = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theSearchParam.getName(), join, predicateNumeric, theRequestPartitionId);
codePredicates.add(predicateOuter);
} else {
throw new IllegalArgumentException(Msg.code(1000) + "Invalid token type: " + nextOr.getClass());
}
}
Predicate predicate = myCriteriaBuilder.or(toArray(codePredicates));
myQueryStack.addPredicateWithImplicitTypeSelection(predicate);
return predicate;
}
}

View File

@ -1,198 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
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 org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Predicate;
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;
@Component
@Scope("prototype")
public class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicateBuilder {
public PredicateBuilderQuantity(LegacySearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}
@Override
public Predicate addPredicate(String theResourceName,
RuntimeSearchParam theSearchParam,
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation theOperation,
RequestPartitionId theRequestPartitionId) {
From<?, ResourceIndexedSearchParamQuantity> join = myQueryStack.createJoin(SearchBuilderJoinEnum.QUANTITY, theSearchParam.getName());
if (theList.get(0).getMissing() != null) {
addPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), join, theRequestPartitionId);
return null;
}
List<Predicate> codePredicates = new ArrayList<>();
addPartitionIdPredicate(theRequestPartitionId, join, codePredicates);
for (IQueryParameterType nextOr : theList) {
Predicate singleCode = createPredicateQuantity(nextOr, theResourceName, theSearchParam.getName(), myCriteriaBuilder, join, theOperation, theRequestPartitionId);
codePredicates.add(singleCode);
}
Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates));
myQueryStack.addPredicateWithImplicitTypeSelection(retVal);
return retVal;
}
public Predicate createPredicateQuantity(IQueryParameterType theParam,
String theResourceName,
String theParamName,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamQuantity> theFrom,
RequestPartitionId theRequestPartitionId) {
return createPredicateQuantity(theParam,
theResourceName,
theParamName,
theBuilder,
theFrom,
null,
theRequestPartitionId);
}
private Predicate createPredicateQuantity(IQueryParameterType theParam,
String theResourceName,
String theParamName,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamQuantity> theFrom,
SearchFilterParser.CompareOperation operation,
RequestPartitionId theRequestPartitionId) {
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(Msg.code(1045) + "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(Msg.code(1046) + "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, theRequestPartitionId);
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, theRequestPartitionId);
}
Predicate hashPredicate;
if (!isBlank(systemValue) && !isBlank(unitsValue)) {
long hash = ResourceIndexedSearchParamQuantity.calculateHashSystemAndUnits(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, systemValue, unitsValue);
hashPredicate = myCriteriaBuilder.equal(theFrom.get("myHashIdentitySystemAndUnits"), hash);
} else if (!isBlank(unitsValue)) {
long hash = ResourceIndexedSearchParamQuantity.calculateHashUnits(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, unitsValue);
hashPredicate = myCriteriaBuilder.equal(theFrom.get("myHashIdentityAndUnits"), hash);
} else {
long hash = BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName);
hashPredicate = myCriteriaBuilder.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, theRequestPartitionId);
return theBuilder.and(hashPredicate, numericPredicate);
}
}

View File

@ -1,135 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.r4.model.IdType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.annotation.Nullable;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@Component
@Scope("prototype")
public class PredicateBuilderResourceId extends BasePredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderResourceId.class);
@Autowired
IIdHelperService myIdHelperService;
public PredicateBuilderResourceId(LegacySearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}
@Nullable
Predicate addPredicateResourceId(List<List<IQueryParameterType>> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
Predicate nextPredicate = createPredicate(theResourceName, theValues, theOperation, theRequestPartitionId);
if (nextPredicate != null) {
myQueryStack.addPredicate(nextPredicate);
return nextPredicate;
}
return null;
}
@Nullable
private Predicate createPredicate(String theResourceName, List<List<IQueryParameterType>> theValues, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
Predicate nextPredicate = null;
Set<ResourcePersistentId> allOrPids = null;
for (List<? extends IQueryParameterType> nextValue : theValues) {
Set<ResourcePersistentId> orPids = new HashSet<>();
boolean haveValue = false;
for (IQueryParameterType next : nextValue) {
String value = next.getValueAsQueryToken(myContext);
if (value != null && value.startsWith("|")) {
value = value.substring(1);
}
IdType valueAsId = new IdType(value);
if (isNotBlank(value)) {
haveValue = true;
try {
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(theRequestPartitionId, theResourceName, valueAsId.getIdPart());
orPids.add(pid);
} catch (ResourceNotFoundException e) {
// This is not an error in a search, it just results in no matchesFhirResourceDaoR4InterceptorTest
ourLog.debug("Resource ID {} was requested but does not exist", valueAsId.getIdPart());
}
}
}
if (haveValue) {
if (allOrPids == null) {
allOrPids = orPids;
} else {
allOrPids.retainAll(orPids);
}
}
}
if (allOrPids != null && allOrPids.isEmpty()) {
// This will never match
nextPredicate = myCriteriaBuilder.equal(myQueryStack.getResourcePidColumn(), -1);
} else if (allOrPids != null) {
SearchFilterParser.CompareOperation operation = defaultIfNull(theOperation, SearchFilterParser.CompareOperation.eq);
assert operation == SearchFilterParser.CompareOperation.eq || operation == SearchFilterParser.CompareOperation.ne;
List<Predicate> codePredicates = new ArrayList<>();
switch (operation) {
default:
case eq:
codePredicates.add(myQueryStack.getResourcePidColumn().in(ResourcePersistentId.toLongList(allOrPids)));
nextPredicate = myCriteriaBuilder.and(toArray(codePredicates));
break;
case ne:
codePredicates.add(myQueryStack.getResourcePidColumn().in(ResourcePersistentId.toLongList(allOrPids)).not());
nextPredicate = myCriteriaBuilder.and(toArray(codePredicates));
break;
}
}
return nextPredicate;
}
}

View File

@ -1,222 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
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 ca.uhn.fhir.util.StringUtil;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
@Component
@Scope("prototype")
public class PredicateBuilderString extends BasePredicateBuilder implements IPredicateBuilder {
public PredicateBuilderString(LegacySearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}
@Override
public Predicate addPredicate(String theResourceName,
RuntimeSearchParam theSearchParam,
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation theOperation,
RequestPartitionId theRequestPartitionId) {
From<?, ResourceIndexedSearchParamString> join = myQueryStack.createJoin(SearchBuilderJoinEnum.STRING, theSearchParam.getName());
if (theList.get(0).getMissing() != null) {
addPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), join, theRequestPartitionId);
return null;
}
List<Predicate> codePredicates = new ArrayList<>();
addPartitionIdPredicate(theRequestPartitionId, join, codePredicates);
for (IQueryParameterType nextOr : theList) {
Predicate singleCode = createPredicateString(nextOr, theResourceName, theSearchParam, myCriteriaBuilder, join, theOperation, theRequestPartitionId);
codePredicates.add(singleCode);
}
Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates));
myQueryStack.addPredicateWithImplicitTypeSelection(retVal);
return retVal;
}
public Predicate createPredicateString(IQueryParameterType theParameter,
String theResourceName,
RuntimeSearchParam theSearchParam,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamString> theFrom,
RequestPartitionId theRequestPartitionId) {
return createPredicateString(theParameter,
theResourceName,
theSearchParam,
theBuilder,
theFrom,
null,
theRequestPartitionId);
}
private Predicate createPredicateString(IQueryParameterType theParameter,
String theResourceName,
RuntimeSearchParam theSearchParam,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamString> theFrom,
SearchFilterParser.CompareOperation operation,
RequestPartitionId theRequestPartitionId) {
String rawSearchTerm;
String paramName = theSearchParam.getName();
if (theParameter instanceof TokenParam) {
TokenParam id = (TokenParam) theParameter;
if (!id.isText()) {
throw new IllegalStateException(Msg.code(1047) + "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(Msg.code(1048) + ":contains modifier is disabled on this server");
}
} else {
rawSearchTerm = theSearchParam.encode(rawSearchTerm);
}
} else if (theParameter instanceof IPrimitiveDatatype<?>) {
IPrimitiveDatatype<?> id = (IPrimitiveDatatype<?>) theParameter;
rawSearchTerm = id.getValueAsString();
} else {
throw new IllegalArgumentException(Msg.code(1049) + "Invalid token type: " + theParameter.getClass());
}
if (rawSearchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
throw new InvalidRequestException(Msg.code(1050) + "Parameter[" + paramName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed ("
+ ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm);
}
if (myDontUseHashesForSearch) {
String likeExpression = StringUtil.normalizeStringForSearchIndexing(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, paramName, theFrom, singleCode, theRequestPartitionId);
}
boolean exactMatch = theParameter instanceof StringParam && ((StringParam) theParameter).isExact();
if (exactMatch) {
// Exact match
Long hash = ResourceIndexedSearchParamString.calculateHashExact(getPartitionSettings(), theRequestPartitionId, theResourceName, paramName, rawSearchTerm);
return theBuilder.equal(theFrom.get("myHashExact").as(Long.class), hash);
} else {
// Normalized Match
String normalizedString = StringUtil.normalizeStringForSearchIndexing(rawSearchTerm);
String likeExpression;
if ((theParameter instanceof StringParam) &&
(((((StringParam) theParameter).isContains()) &&
(myDaoConfig.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(getPartitionSettings(), theRequestPartitionId, myDaoConfig.getModelConfig(), theResourceName, paramName, 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, paramName, theFrom, singleCode, theRequestPartitionId);
} else if (operation == SearchFilterParser.CompareOperation.eq) {
Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(getPartitionSettings(), theRequestPartitionId, myDaoConfig.getModelConfig(), theResourceName, paramName, 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, paramName, theFrom, singleCode, theRequestPartitionId);
} else if (operation == SearchFilterParser.CompareOperation.gt) {
Predicate singleCode = theBuilder.greaterThan(theFrom.get("myValueNormalized").as(String.class), likeExpression);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId);
} else if (operation == SearchFilterParser.CompareOperation.lt) {
Predicate singleCode = theBuilder.lessThan(theFrom.get("myValueNormalized").as(String.class), likeExpression);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId);
} else if (operation == SearchFilterParser.CompareOperation.ge) {
Predicate singleCode = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueNormalized").as(String.class), likeExpression);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId);
} else if (operation == SearchFilterParser.CompareOperation.le) {
Predicate singleCode = theBuilder.lessThanOrEqualTo(theFrom.get("myValueNormalized").as(String.class), likeExpression);
predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId);
} else {
throw new IllegalArgumentException(Msg.code(1051) + "Don't yet know how to handle operation " + operation + " on a string");
}
return predicate;
}
}
}

View File

@ -1,196 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
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 org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@Component
@Scope("prototype")
public class PredicateBuilderTag extends BasePredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderTag.class);
public PredicateBuilderTag(LegacySearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}
void addPredicateTag(List<List<IQueryParameterType>> theList, String theParamName, RequestPartitionId theRequestPartitionId) {
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(Msg.code(1030) + "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()));
}
}
}
}
}
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(Msg.code(1031) + "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 = myQueryStack.subqueryForTagNegation();
Root<ResourceTag> subQfrom = subQ.from(ResourceTag.class);
subQ.select(subQfrom.get("myResourceId").as(Long.class));
myQueryStack.addPredicate(
myCriteriaBuilder.not(
myCriteriaBuilder.in(
myQueryStack.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, myCriteriaBuilder, tagType, tokens);
defJoin.where(tagListPredicate);
continue;
}
From<?, ResourceTag> tagJoin = myQueryStack.createJoin(SearchBuilderJoinEnum.RESOURCE_TAGS, null);
From<ResourceTag, TagDefinition> defJoin = tagJoin.join("myTag");
Predicate tagListPredicate = createPredicateTagList(defJoin, myCriteriaBuilder, tagType, tokens);
List<Predicate> predicates = Lists.newArrayList(tagListPredicate);
if (theRequestPartitionId != null) {
addPartitionIdPredicate(theRequestPartitionId, tagJoin, predicates);
}
myQueryStack.addPredicates(predicates);
}
}
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

@ -1,393 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor;
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 ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.util.FhirVersionIndependentConcept;
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 org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@Component
@Scope("prototype")
public
class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBuilder {
private final PredicateBuilder myPredicateBuilder;
@Autowired
private ITermReadSvc myTerminologySvc;
@Autowired
private ModelConfig myModelConfig;
public PredicateBuilderToken(LegacySearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) {
super(theSearchBuilder);
myPredicateBuilder = thePredicateBuilder;
}
@Override
public Predicate addPredicate(String theResourceName,
RuntimeSearchParam theSearchParam,
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation theOperation,
RequestPartitionId theRequestPartitionId) {
if (theList.get(0).getMissing() != null) {
From<?, ResourceIndexedSearchParamToken> join = myQueryStack.createJoin(SearchBuilderJoinEnum.TOKEN, theSearchParam.getName());
addPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), join, theRequestPartitionId);
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()) {
// Check whether the :text modifier is actually enabled here
boolean tokenTextIndexingEnabled = BaseSearchParamExtractor.tokenTextIndexingEnabledForSearchParam(myModelConfig, theSearchParam);
if (!tokenTextIndexingEnabled) {
String msg;
if (myModelConfig.isSuppressStringIndexingInTokens()) {
msg = myContext.getLocalizer().getMessage(PredicateBuilderToken.class, "textModifierDisabledForServer");
} else {
msg = myContext.getLocalizer().getMessage(PredicateBuilderToken.class, "textModifierDisabledForSearchParam");
}
throw new MethodNotAllowedException(Msg.code(1032) + msg);
}
myPredicateBuilder.addPredicateString(theResourceName, theSearchParam, theList, theOperation, theRequestPartitionId);
break;
}
}
tokens.add(nextOr);
}
if (tokens.isEmpty()) {
return null;
}
From<?, ResourceIndexedSearchParamToken> join = myQueryStack.createJoin(SearchBuilderJoinEnum.TOKEN, theSearchParam.getName());
addPartitionIdPredicate(theRequestPartitionId, join, codePredicates);
Collection<Predicate> singleCode = createPredicateToken(tokens, theResourceName, theSearchParam, myCriteriaBuilder, join, theOperation, theRequestPartitionId);
assert singleCode != null;
codePredicates.addAll(singleCode);
Predicate spPredicate = myCriteriaBuilder.or(toArray(codePredicates));
myQueryStack.addPredicateWithImplicitTypeSelection(spPredicate);
return spPredicate;
}
public Collection<Predicate> createPredicateToken(Collection<IQueryParameterType> theParameters,
String theResourceName,
RuntimeSearchParam theSearchParam,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamToken> theFrom,
RequestPartitionId theRequestPartitionId) {
return createPredicateToken(
theParameters,
theResourceName,
theSearchParam,
theBuilder,
theFrom,
null,
theRequestPartitionId);
}
private Collection<Predicate> createPredicateToken(Collection<IQueryParameterType> theParameters,
String theResourceName,
RuntimeSearchParam theSearchParam,
CriteriaBuilder theBuilder,
From<?, ResourceIndexedSearchParamToken> theFrom,
SearchFilterParser.CompareOperation operation,
RequestPartitionId theRequestPartitionId) {
final List<FhirVersionIndependentConcept> codes = new ArrayList<>();
String paramName = theSearchParam.getName();
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(Msg.code(1033) + "Invalid token type: " + nextParameter.getClass());
}
if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
throw new InvalidRequestException(Msg.code(1034) + "Parameter[" + paramName + "] 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(Msg.code(1035) + "Parameter[" + paramName + "] 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.expandValueSetIntoConceptList(null, code));
} else if (modifier == TokenParamModifier.ABOVE) {
system = determineSystemIfMissing(theSearchParam, code, system);
validateHaveSystemAndCodeForToken(paramName, code, system);
codes.addAll(myTerminologySvc.findCodesAbove(system, code));
} else if (modifier == TokenParamModifier.BELOW) {
system = determineSystemIfMissing(theSearchParam, code, system);
validateHaveSystemAndCodeForToken(paramName, code, system);
codes.addAll(myTerminologySvc.findCodesBelow(system, code));
} else {
codes.add(new FhirVersionIndependentConcept(system, code));
}
}
List<FhirVersionIndependentConcept> 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<FhirVersionIndependentConcept> systemOnlyCodes = sortedCodesList.stream().filter(t -> isBlank(t.getCode())).collect(Collectors.toList());
if (!systemOnlyCodes.isEmpty()) {
retVal.add(addPredicate(theResourceName, paramName, theBuilder, theFrom, systemOnlyCodes, modifier, SearchBuilderTokenModeEnum.SYSTEM_ONLY, theRequestPartitionId));
}
// Code only
List<FhirVersionIndependentConcept> codeOnlyCodes = sortedCodesList.stream().filter(t -> t.getSystem() == null).collect(Collectors.toList());
if (!codeOnlyCodes.isEmpty()) {
retVal.add(addPredicate(theResourceName, paramName, theBuilder, theFrom, codeOnlyCodes, modifier, SearchBuilderTokenModeEnum.VALUE_ONLY, theRequestPartitionId));
}
// System and code
List<FhirVersionIndependentConcept> systemAndCodeCodes = sortedCodesList.stream().filter(t -> isNotBlank(t.getCode()) && t.getSystem() != null).collect(Collectors.toList());
if (!systemAndCodeCodes.isEmpty()) {
retVal.add(addPredicate(theResourceName, paramName, theBuilder, theFrom, systemAndCodeCodes, modifier, SearchBuilderTokenModeEnum.SYSTEM_AND_VALUE, theRequestPartitionId));
}
return retVal;
}
private String determineSystemIfMissing(RuntimeSearchParam theSearchParam, String code, String theSystem) {
String retVal = theSystem;
if (retVal == null) {
if (theSearchParam != null) {
Set<String> valueSetUris = Sets.newHashSet();
for (String nextPath : theSearchParam.getPathsSplit()) {
if (!nextPath.startsWith(myResourceType + ".")) {
continue;
}
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();
ValueSetExpansionOptions options = new ValueSetExpansionOptions()
.setFailOnMissingCodeSystem(false);
List<FhirVersionIndependentConcept> candidateCodes = myTerminologySvc.expandValueSetIntoConceptList(options, valueSet);
for (FhirVersionIndependentConcept 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(LegacySearchBuilder.class, "invalidCodeMissingSystem", theParamName, systemDesc, codeDesc);
throw new InvalidRequestException(Msg.code(1036) + msg);
}
if (isBlank(theSystem)) {
String msg = myContext.getLocalizer().getMessage(LegacySearchBuilder.class, "invalidCodeMissingCode", theParamName, systemDesc, codeDesc);
throw new InvalidRequestException(Msg.code(1037) + msg);
}
}
private Predicate addPredicate(String theResourceName, String theParamName, CriteriaBuilder theBuilder, From<?, ResourceIndexedSearchParamToken> theFrom, List<FhirVersionIndependentConcept> theTokens, TokenParamModifier theModifier, SearchBuilderTokenModeEnum theTokenMode, RequestPartitionId theRequestPartitionId) {
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 (FhirVersionIndependentConcept 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, theRequestPartitionId);
}
/*
* 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(getPartitionSettings(), theRequestPartitionId, 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(getPartitionSettings(), theRequestPartitionId, 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(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, t.getSystem(), t.getCode()))
.collect(Collectors.toList());
break;
}
/*
* Note: At one point we had an IF-ELSE here that did an equals if there was only 1 value, and an IN if there
* was more than 1. This caused a performance regression for some reason in Postgres though. So maybe simpler
* is better..
*/
Predicate predicate = hashField.in(values);
if (theModifier == TokenParamModifier.NOT) {
Predicate identityPredicate = theBuilder.equal(theFrom.get("myHashIdentity").as(Long.class), BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, 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 myCriteriaBuilder.isNull(theExpression);
}
return myCriteriaBuilder.equal(theExpression, theCode);
}
}

View File

@ -1,189 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
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.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 org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Component
@Scope("prototype")
public class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuilder {
private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderUri.class);
@Autowired
private IResourceIndexedSearchParamUriDao myResourceIndexedSearchParamUriDao;
public PredicateBuilderUri(LegacySearchBuilder theSearchBuilder) {
super(theSearchBuilder);
}
@Override
public Predicate addPredicate(String theResourceName,
RuntimeSearchParam theSearchParam,
List<? extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation operation,
RequestPartitionId theRequestPartitionId) {
String paramName = theSearchParam.getName();
From<?, ResourceIndexedSearchParamUri> join = myQueryStack.createJoin(SearchBuilderJoinEnum.URI, paramName);
if (theList.get(0).getMissing() != null) {
addPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), join, theRequestPartitionId);
return null;
}
List<Predicate> codePredicates = new ArrayList<>();
addPartitionIdPredicate(theRequestPartitionId, join, codePredicates);
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, paramName);
Collection<String> candidates = myResourceIndexedSearchParamUriDao.findAllByResourceTypeAndParamName(myResourceName, paramName);
List<String> toFind = new ArrayList<>();
for (String next : candidates) {
if (value.length() >= next.length()) {
if (value.startsWith(next)) {
toFind.add(next);
}
}
}
if (toFind.isEmpty()) {
continue;
}
Predicate uriPredicate = join.get("myUri").as(String.class).in(toFind);
Predicate hashAndUriPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, join, uriPredicate, theRequestPartitionId);
codePredicates.add(hashAndUriPredicate);
} else if (param.getQualifier() == UriParamQualifierEnum.BELOW) {
Predicate uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value));
Predicate hashAndUriPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, join, uriPredicate, theRequestPartitionId);
codePredicates.add(hashAndUriPredicate);
} else {
if (myDontUseHashesForSearch) {
Predicate predicate = myCriteriaBuilder.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(getPartitionSettings(), theRequestPartitionId, theResourceName, paramName, value);
Predicate hashPredicate = myCriteriaBuilder.equal(join.get("myHashUri"), hashUri);
codePredicates.add(hashPredicate);
} else if (operation == SearchFilterParser.CompareOperation.ne) {
uriPredicate = myCriteriaBuilder.notEqual(join.get("myUri").as(String.class), value);
} else if (operation == SearchFilterParser.CompareOperation.co) {
uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createLeftAndRightMatchLikeExpression(value));
} else if (operation == SearchFilterParser.CompareOperation.gt) {
uriPredicate = myCriteriaBuilder.greaterThan(join.get("myUri").as(String.class), value);
} else if (operation == SearchFilterParser.CompareOperation.lt) {
uriPredicate = myCriteriaBuilder.lessThan(join.get("myUri").as(String.class), value);
} else if (operation == SearchFilterParser.CompareOperation.ge) {
uriPredicate = myCriteriaBuilder.greaterThanOrEqualTo(join.get("myUri").as(String.class), value);
} else if (operation == SearchFilterParser.CompareOperation.le) {
uriPredicate = myCriteriaBuilder.lessThanOrEqualTo(join.get("myUri").as(String.class), value);
} else if (operation == SearchFilterParser.CompareOperation.sw) {
uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value));
} else if (operation == SearchFilterParser.CompareOperation.ew) {
uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createRightMatchLikeExpression(value));
} else {
throw new IllegalArgumentException(Msg.code(1070) + String.format("Unsupported operator specified in _filter clause, %s",
operation.toString()));
}
if (uriPredicate != null) {
long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, theResourceName, paramName);
Predicate hashIdentityPredicate = myCriteriaBuilder.equal(join.get("myHashIdentity"), hashIdentity);
codePredicates.add(myCriteriaBuilder.and(hashIdentityPredicate, uriPredicate));
}
}
}
} else {
throw new IllegalArgumentException(Msg.code(1071) + "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 = myCriteriaBuilder.isNull(join.get("myMissing").as(String.class));
myQueryStack.addPredicateWithImplicitTypeSelection(predicate);
return null;
}
Predicate orPredicate = myCriteriaBuilder.or(toArray(codePredicates));
Predicate outerPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName,
paramName,
join,
orPredicate,
theRequestPartitionId);
myQueryStack.addPredicateWithImplicitTypeSelection(outerPredicate);
return outerPredicate;
}
}

View File

@ -1,35 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public enum SearchBuilderJoinEnum {
DATE,
NUMBER,
QUANTITY,
REFERENCE,
STRING,
TOKEN,
URI,
COORDS,
HAS,
FORCED_ID, PRESENCE, COMPOSITE_UNIQUE, RESOURCE_TAGS, PROVENANCE
}

View File

@ -1,55 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
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

@ -1,27 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public enum SearchBuilderTokenModeEnum {
SYSTEM_ONLY,
VALUE_ONLY,
SYSTEM_AND_VALUE
}

View File

@ -1,137 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate.querystack;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.dao.predicate.IndexJoins;
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinEnum;
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinKey;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import javax.persistence.criteria.AbstractQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
abstract class QueryRootEntry {
private final ArrayList<Predicate> myPredicates = new ArrayList<>();
private final IndexJoins myIndexJoins = new IndexJoins();
private final CriteriaBuilder myCriteriaBuilder;
private boolean myHasImplicitTypeSelection;
private Map<String, From<?, ResourceIndexedSearchParamDate>> myJoinMap;
QueryRootEntry(CriteriaBuilder theCriteriaBuilder) {
myCriteriaBuilder = theCriteriaBuilder;
}
boolean isHasImplicitTypeSelection() {
return myHasImplicitTypeSelection;
}
void setHasImplicitTypeSelection(boolean theHasImplicitTypeSelection) {
myHasImplicitTypeSelection = theHasImplicitTypeSelection;
}
Optional<Join<?, ?>> getIndexJoin(SearchBuilderJoinKey theKey) {
return Optional.ofNullable(myIndexJoins.get(theKey));
}
void addPredicate(Predicate thePredicate) {
myPredicates.add(thePredicate);
}
void addPredicates(List<Predicate> thePredicates) {
myPredicates.addAll(thePredicates);
}
Predicate addNeverMatchingPredicate() {
Predicate predicate = myCriteriaBuilder.equal(getResourcePidColumn(), -1L);
clearPredicates();
addPredicate(predicate);
return predicate;
}
Predicate[] getPredicateArray() {
return myPredicates.toArray(new Predicate[0]);
}
void putIndex(SearchBuilderJoinKey theKey, Join<?, ?> theJoin) {
myIndexJoins.put(theKey, theJoin);
}
void clearPredicates() {
myPredicates.clear();
}
List<Predicate> getPredicates() {
return Collections.unmodifiableList(myPredicates);
}
<Y> Path<Y> get(String theAttributeName) {
return getRoot().get(theAttributeName);
}
AbstractQuery<Long> pop() {
Predicate[] predicateArray = getPredicateArray();
if (predicateArray.length == 1) {
getQueryRoot().where(predicateArray[0]);
} else {
getQueryRoot().where(myCriteriaBuilder.and(predicateArray));
}
return getQueryRoot();
}
public Map<String, From<?, ResourceIndexedSearchParamDate>> getJoinMap() {
Map<String, From<?, ResourceIndexedSearchParamDate>> retVal = myJoinMap;
if (retVal==null) {
retVal = new HashMap<>();
myJoinMap = retVal;
}
return retVal;
}
abstract void orderBy(List<Order> theOrders);
abstract Expression<Date> getLastUpdatedColumn();
abstract <T> From<?, T> createJoin(SearchBuilderJoinEnum theType, String theSearchParameterName);
abstract AbstractQuery<Long> getQueryRoot();
abstract Root<?> getRoot();
abstract Expression<Long> getResourcePidColumn();
abstract Subquery<Long> subqueryForTagNegation();
}

View File

@ -1,146 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate.querystack;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinEnum;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndex;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import org.apache.commons.lang3.Validate;
import javax.persistence.criteria.AbstractQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import java.util.Date;
import java.util.List;
public class QueryRootEntryIndexTable extends QueryRootEntry {
private final Subquery<Long> myQuery;
private Root<? extends BaseResourceIndex> myRoot;
private SearchBuilderJoinEnum myParamType;
private Expression<Long> myResourcePidColumn;
public QueryRootEntryIndexTable(CriteriaBuilder theCriteriaBuilder, QueryRootEntry theParent) {
super(theCriteriaBuilder);
AbstractQuery<Long> queryRoot = theParent.getQueryRoot();
myQuery = queryRoot.subquery(Long.class);
}
@Override
void orderBy(List<Order> theOrders) {
throw new IllegalStateException(Msg.code(1072));
}
@Override
Expression<Date> getLastUpdatedColumn() {
return getRoot().get("myUpdated").as(Date.class);
}
@Override
<T> From<?, T> createJoin(SearchBuilderJoinEnum theType, String theSearchParameterName) {
if (myParamType == null) {
switch (theType) {
case REFERENCE:
myRoot = myQuery.from(ResourceLink.class);
myResourcePidColumn = myRoot.get("mySourceResourcePid").as(Long.class);
myParamType = SearchBuilderJoinEnum.REFERENCE;
break;
case NUMBER:
myRoot = myQuery.from(ResourceIndexedSearchParamNumber.class);
myResourcePidColumn = myRoot.get("myResourcePid").as(Long.class);
myParamType = SearchBuilderJoinEnum.NUMBER;
break;
case DATE:
myRoot = myQuery.from(ResourceIndexedSearchParamDate.class);
myResourcePidColumn = myRoot.get("myResourcePid").as(Long.class);
myParamType = SearchBuilderJoinEnum.DATE;
break;
case STRING:
myRoot = myQuery.from(ResourceIndexedSearchParamString.class);
myResourcePidColumn = myRoot.get("myResourcePid").as(Long.class);
myParamType = SearchBuilderJoinEnum.STRING;
break;
case TOKEN:
myRoot = myQuery.from(ResourceIndexedSearchParamToken.class);
myResourcePidColumn = myRoot.get("myResourcePid").as(Long.class);
myParamType = SearchBuilderJoinEnum.TOKEN;
break;
case QUANTITY:
myRoot = myQuery.from(ResourceIndexedSearchParamQuantity.class);
myResourcePidColumn = myRoot.get("myResourcePid").as(Long.class);
myParamType = SearchBuilderJoinEnum.QUANTITY;
break;
case URI:
myRoot = myQuery.from(ResourceIndexedSearchParamUri.class);
myResourcePidColumn = myRoot.get("myResourcePid").as(Long.class);
myParamType = SearchBuilderJoinEnum.URI;
break;
case COORDS:
myRoot = myQuery.from(ResourceIndexedSearchParamCoords.class);
myResourcePidColumn = myRoot.get("myResourcePid").as(Long.class);
myParamType = SearchBuilderJoinEnum.COORDS;
break;
default:
throw new IllegalStateException(Msg.code(1073));
}
myQuery.select(myResourcePidColumn);
}
Validate.isTrue(theType == myParamType, "Wanted %s but got %s for %s", myParamType, theType, theSearchParameterName);
return (From<?, T>) myRoot;
}
@Override
AbstractQuery<Long> getQueryRoot() {
Validate.isTrue(myQuery != null);
return myQuery;
}
@Override
Root<?> getRoot() {
Validate.isTrue(myRoot != null);
return myRoot;
}
@Override
public Expression<Long> getResourcePidColumn() {
Validate.isTrue(myResourcePidColumn != null);
return myResourcePidColumn;
}
@Override
public Subquery<Long> subqueryForTagNegation() {
throw new IllegalStateException(Msg.code(1074));
}
}

View File

@ -1,207 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate.querystack;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinEnum;
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinKey;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import javax.persistence.criteria.AbstractQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import java.util.Date;
import java.util.List;
class QueryRootEntryResourceTable extends QueryRootEntry {
private final CriteriaBuilder myCriteriaBuilder;
private final AbstractQuery<Long> myQuery;
private final SearchParameterMap mySearchParameterMap;
private final RequestPartitionId myRequestPartitionId;
private final String myResourceType;
/**
* This method will ddd a predicate to make sure we only include non-deleted resources, and only include
* resources of the right type.
*
* If we have any joins to index tables, we get this behaviour already guaranteed so we don't
* need an explicit predicate for it.
*/
@Override
AbstractQuery<Long> pop() {
if (!isHasImplicitTypeSelection()) {
if (mySearchParameterMap.getEverythingMode() == null) {
addPredicate(myCriteriaBuilder.equal(getRoot().get("myResourceType"), myResourceType));
}
addPredicate(myCriteriaBuilder.isNull(getRoot().get("myDeleted")));
if (!myRequestPartitionId.isAllPartitions()) {
if (!myRequestPartitionId.isDefaultPartition()) {
addPredicate(getRoot().get("myPartitionIdValue").as(Integer.class).in(myRequestPartitionId.getPartitionIds()));
} else {
addPredicate(myCriteriaBuilder.isNull(getRoot().get("myPartitionIdValue").as(Integer.class)));
}
}
}
return super.pop();
}
private final Root<ResourceTable> myResourceTableRoot;
/**
* Root query constructor
*/
QueryRootEntryResourceTable(CriteriaBuilder theCriteriaBuilder, boolean theDistinct, boolean theCountQuery, SearchParameterMap theSearchParameterMap, String theResourceType, RequestPartitionId theRequestPartitionId) {
super(theCriteriaBuilder);
myCriteriaBuilder = theCriteriaBuilder;
mySearchParameterMap = theSearchParameterMap;
myRequestPartitionId = theRequestPartitionId;
myResourceType = theResourceType;
CriteriaQuery<Long> query = myCriteriaBuilder.createQuery(Long.class);
myResourceTableRoot = query.from(ResourceTable.class);
if (theCountQuery) {
query.multiselect(myCriteriaBuilder.countDistinct(myResourceTableRoot));
} else if (theDistinct) {
query.distinct(true).multiselect(get("myId").as(Long.class));
} else {
query.multiselect(get("myId").as(Long.class));
}
myQuery = query;
}
/**
* Subquery constructor
*/
QueryRootEntryResourceTable(CriteriaBuilder theCriteriaBuilder, QueryRootEntry theParent, SearchParameterMap theSearchParameterMap, String theResourceType, RequestPartitionId theRequestPartitionId) {
super(theCriteriaBuilder);
myCriteriaBuilder = theCriteriaBuilder;
mySearchParameterMap = theSearchParameterMap;
myRequestPartitionId = theRequestPartitionId;
myResourceType = theResourceType;
AbstractQuery<Long> queryRoot = theParent.getQueryRoot();
Subquery<Long> query = queryRoot.subquery(Long.class);
myQuery = query;
myResourceTableRoot = myQuery.from(ResourceTable.class);
query.select(myResourceTableRoot.get("myId").as(Long.class));
}
@Override
void orderBy(List<Order> theOrders) {
assert myQuery instanceof CriteriaQuery;
((CriteriaQuery<?>)myQuery).orderBy(theOrders);
}
@Override
Expression<Date> getLastUpdatedColumn() {
return myResourceTableRoot.get("myUpdated").as(Date.class);
}
@SuppressWarnings("unchecked")
@Override
<T> From<?, T> createJoin(SearchBuilderJoinEnum theType, String theSearchParameterName) {
Join<?,?> 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;
case HAS:
join = myResourceTableRoot.join("myResourceLinksAsTarget", JoinType.LEFT);
break;
case PROVENANCE:
join = myResourceTableRoot.join("myProvenance", JoinType.LEFT);
break;
case FORCED_ID:
join = myResourceTableRoot.join("myForcedId", JoinType.LEFT);
break;
case PRESENCE:
join = myResourceTableRoot.join("mySearchParamPresents", JoinType.LEFT);
break;
case COMPOSITE_UNIQUE:
join = myResourceTableRoot.join("myParamsComboStringUnique", JoinType.LEFT);
break;
case RESOURCE_TAGS:
join = myResourceTableRoot.join("myTags", JoinType.LEFT);
break;
}
SearchBuilderJoinKey key = new SearchBuilderJoinKey(theSearchParameterName, theType);
putIndex(key, join);
return (From<?, T>) join;
}
@Override
AbstractQuery<Long> getQueryRoot() {
return myQuery;
}
@Override
Root<ResourceTable> getRoot() {
return myResourceTableRoot;
}
@Override
public Expression<Long> getResourcePidColumn() {
return myResourceTableRoot.get("myId").as(Long.class);
}
@Override
public Subquery<Long> subqueryForTagNegation() {
return myQuery.subquery(Long.class);
}
}

View File

@ -1,303 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate.querystack;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinEnum;
import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinKey;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import org.apache.commons.lang3.Validate;
import javax.persistence.criteria.AbstractQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Stack;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* This class represents a SQL SELECT statement that is selecting for resource PIDs, ie.
* the <code>RES_ID</code> column on the <code>HFJ_RESOURCE</code> ({@link ca.uhn.fhir.jpa.model.entity.ResourceTable})
* table.
* <p>
* We add predicates (WHERE A=B) to it, and can join other tables to it as well. At the root of the query
* we are typically doing a <code>select RES_ID from HFJ_RESOURCE where (....)</code> and this class
* is used to build the <i>where</i> clause. In the case of subqueries though, we may be performing a
* select on a different table since many tables have a column with a FK dependency on RES_ID.
* </p>
*/
public class QueryStack {
private final Stack<QueryRootEntry> myQueryRootStack = new Stack<>();
private final CriteriaBuilder myCriteriaBuilder;
private final SearchParameterMap mySearchParameterMap;
private final RequestPartitionId myRequestPartitionId;
private final String myResourceType;
/**
* Constructor
*/
public QueryStack(CriteriaBuilder theCriteriaBuilder, String theResourceType, SearchParameterMap theSearchParameterMap, RequestPartitionId theRequestPartitionId) {
assert theCriteriaBuilder != null;
assert isNotBlank(theResourceType);
assert theSearchParameterMap != null;
assert theRequestPartitionId != null;
myCriteriaBuilder = theCriteriaBuilder;
mySearchParameterMap = theSearchParameterMap;
myRequestPartitionId = theRequestPartitionId;
myResourceType = theResourceType;
}
/**
* Add a new <code>select RES_ID from HFJ_RESOURCE</code> to the stack. All predicates added to the {@literal QueryRootStack}
* will be added to this select clause until {@link #pop()} is called.
* <p>
* This method must only be called when the stack is empty.
* </p>
*/
public void pushResourceTableQuery() {
assert myQueryRootStack.isEmpty();
myQueryRootStack.push(new QueryRootEntryResourceTable(myCriteriaBuilder, false, false, mySearchParameterMap, myResourceType, myRequestPartitionId));
}
/**
* Add a new <code>select DISTINCT RES_ID from HFJ_RESOURCE</code> to the stack. All predicates added to the {@literal QueryRootStack}
* will be added to this select clause until {@link #pop()} is called.
* <p>
* This method must only be called when the stack is empty.
* </p>
*/
public void pushResourceTableDistinctQuery() {
assert myQueryRootStack.isEmpty();
myQueryRootStack.push(new QueryRootEntryResourceTable(myCriteriaBuilder, true, false, mySearchParameterMap, myResourceType, myRequestPartitionId));
}
/**
* Add a new <code>select count(RES_ID) from HFJ_RESOURCE</code> to the stack. All predicates added to the {@literal QueryRootStack}
* will be added to this select clause until {@link #pop()} is called.
* <p>
* This method must only be called when the stack is empty.
* </p>
*/
public void pushResourceTableCountQuery() {
assert myQueryRootStack.isEmpty();
myQueryRootStack.push(new QueryRootEntryResourceTable(myCriteriaBuilder, false, true, mySearchParameterMap, myResourceType, myRequestPartitionId));
}
/**
* Add a new <code>select RES_ID from HFJ_RESOURCE</code> to the stack. All predicates added to the {@literal QueryRootStack}
* will be added to this select clause until {@link #pop()} is called.
* <p>
* This method must only be called when the stack is NOT empty.
* </p>
*/
public void pushResourceTableSubQuery(String theResourceType) {
assert !myQueryRootStack.isEmpty();
myQueryRootStack.push(new QueryRootEntryResourceTable(myCriteriaBuilder, top(), mySearchParameterMap, theResourceType, myRequestPartitionId));
}
/**
* Add a new <code>select RES_ID from (....)</code> to the stack, where the specific table being selected on will be
* determined based on the first call to {@link #createJoin(SearchBuilderJoinEnum, String)}. All predicates added
* to the {@literal QueryRootStack} will be added to this select clause until {@link #pop()} is called.
* <p>
* This method must only be called when the stack is NOT empty.
* </p>
*/
public void pushIndexTableSubQuery() {
assert !myQueryRootStack.isEmpty();
myQueryRootStack.push(new QueryRootEntryIndexTable(myCriteriaBuilder, top()));
}
/**
* This method must be called once all predicates have been added
*/
public AbstractQuery<Long> pop() {
QueryRootEntry element = myQueryRootStack.pop();
return element.pop();
}
/**
* Creates a new SQL join from the current select statement to another table, using the resource PID as the
* joining key
*/
public <T> From<?, T> createJoin(SearchBuilderJoinEnum theType, String theSearchParameterName) {
return top().createJoin(theType, theSearchParameterName);
}
/**
* Returns a join that was previously created by a call to {@link #createJoin(SearchBuilderJoinEnum, String)},
* if one exists for the given key.
*/
public Optional<Join<?, ?>> getExistingJoin(SearchBuilderJoinKey theKey) {
return top().getIndexJoin(theKey);
}
/**
* Gets an attribute (aka a column) from the current select statement.
*
* @param theAttributeName Must be the name of a java field for the entity/table being selected on
*/
public <Y> Path<Y> get(String theAttributeName) {
return top().get(theAttributeName);
}
/**
* Adds a predicate to the current select statement
*/
public void addPredicate(Predicate thePredicate) {
top().addPredicate(thePredicate);
}
/**
* Adds a predicate and marks it as having implicit type selection in it. In other words, call this method if a
* this predicate will ensure:
* <ul>
* <li>Only Resource PIDs for the correct resource type will be selected</li>
* <li>Only Resource PIDs for non-deleted resources will be selected</li>
* </ul>
* Setting this flag is a performance optimization, since it avoids the need for us to explicitly
* add predicates for the two conditions above.
*/
public void addPredicateWithImplicitTypeSelection(Predicate thePredicate) {
setHasImplicitTypeSelection();
addPredicate(thePredicate);
}
/**
* Adds predicates and marks them as having implicit type selection in it. In other words, call this method if a
* this predicate will ensure:
* <ul>
* <li>Only Resource PIDs for the correct resource type will be selected</li>
* <li>Only Resource PIDs for non-deleted resources will be selected</li>
* </ul>
* Setting this flag is a performance optimization, since it avoids the need for us to explicitly
* add predicates for the two conditions above.
*/
public void addPredicatesWithImplicitTypeSelection(List<Predicate> thePredicates) {
setHasImplicitTypeSelection();
addPredicates(thePredicates);
}
/**
* Adds predicate(s) to the current select statement
*/
public void addPredicates(List<Predicate> thePredicates) {
top().addPredicates(thePredicates);
}
/**
* Clear all predicates from the current select statement
*/
public void clearPredicates() {
top().clearPredicates();
}
/**
* Fetch all the current predicates
* <p>
* TODO This should really be package protected, but it is called externally in one spot - We need to clean that up
* at some point.
*/
public List<Predicate> getPredicates() {
return top().getPredicates();
}
private void setHasImplicitTypeSelection() {
top().setHasImplicitTypeSelection(true);
}
/**
* @see #setHasImplicitTypeSelection()
*/
public void clearHasImplicitTypeSelection() {
top().setHasImplicitTypeSelection(false);
}
public boolean isEmpty() {
return myQueryRootStack.isEmpty();
}
/**
* Add an SQL <code>order by</code> expression
*/
public void orderBy(List<Order> theOrders) {
top().orderBy(theOrders);
}
/**
* Fetch the column for the current table root that corresponds to the resource's lastUpdated time
*/
public Expression<Date> getLastUpdatedColumn() {
return top().getLastUpdatedColumn();
}
/**
* Fetch the column for the current table root that corresponds to the resource's PID
*/
public Expression<Long> getResourcePidColumn() {
return top().getResourcePidColumn();
}
public Subquery<Long> subqueryForTagNegation() {
return top().subqueryForTagNegation();
}
private QueryRootEntry top() {
Validate.isTrue(!myQueryRootStack.empty());
return myQueryRootStack.peek();
}
/**
* TODO This class should avoid leaking the internal query root, but we need to do so for how composite search params are
* currently implemented. These only half work in the first place so I'm not going to worry about the fact that
* they rely on a leaky abstraction right now.. But when we get around to implementing composites properly,
* let's not continue this. JA 2020-05-12
*/
public Root<?> getRootForComposite() {
return top().getRoot();
}
/**
* Add a predicate that will never match any resources
*/
public Predicate addNeverMatchingPredicate() {
return top().addNeverMatchingPredicate();
}
public Map<String, From<?, ResourceIndexedSearchParamDate>> getJoinMap() {
return top().getJoinMap();
}
}

View File

@ -10,7 +10,7 @@
* in {@link ca.uhn.fhir.jpa.dao.search.ExtendedHSearchIndexExtractor#extract(org.hl7.fhir.instance.model.api.IBaseResource, ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams)} ()}
*
* <h2>Implementation</h2>
* Both {@link ca.uhn.fhir.jpa.search.builder.SearchBuilder} and {@link ca.uhn.fhir.jpa.dao.LegacySearchBuilder} delegate the
* {@link ca.uhn.fhir.jpa.search.builder.SearchBuilder} delegates the
* search to {@link ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl#doSearch} when active.
* The fulltext search runs first and interprets any search parameters it understands, returning a pid list.
* This pid list is used as a narrowing where clause against the remaining unprocessed search parameters in a jdbc query.

View File

@ -103,7 +103,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.dao.LegacySearchBuilder.toPredicateArray;
import static ca.uhn.fhir.jpa.search.builder.SearchBuilder.toPredicateArray;
import static ca.uhn.fhir.util.StringUtil.toUtf8String;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

View File

@ -35,7 +35,6 @@ import ca.uhn.fhir.jpa.dao.BaseStorageDao;
import ca.uhn.fhir.jpa.dao.IResultIterator;
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.SearchInclude;
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
@ -43,6 +42,7 @@ import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails;
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceLinkPredicateBuilder;
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc;
import ca.uhn.fhir.jpa.search.cache.SearchCacheStatusEnum;
@ -419,13 +419,13 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
}
if (!myDaoRegistry.isResourceTypeSupported(paramType)) {
String resourceTypeMsg = myContext.getLocalizer().getMessageSanitized(PredicateBuilderReference.class, "invalidResourceType", paramType);
String resourceTypeMsg = myContext.getLocalizer().getMessageSanitized(SearchCoordinatorSvcImpl.class, "invalidResourceType", paramType);
String msg = myContext.getLocalizer().getMessage(SearchCoordinatorSvcImpl.class, "invalidInclude", UrlUtil.sanitizeUrlPart(name), UrlUtil.sanitizeUrlPart(value), resourceTypeMsg); // last param is pre-sanitized
throw new InvalidRequestException(Msg.code(2017) + msg);
}
if (isNotBlank(paramTargetType) && !myDaoRegistry.isResourceTypeSupported(paramTargetType)) {
String resourceTypeMsg = myContext.getLocalizer().getMessageSanitized(PredicateBuilderReference.class, "invalidResourceType", paramTargetType);
String resourceTypeMsg = myContext.getLocalizer().getMessageSanitized(SearchCoordinatorSvcImpl.class, "invalidResourceType", paramTargetType);
String msg = myContext.getLocalizer().getMessage(SearchCoordinatorSvcImpl.class, "invalidInclude", UrlUtil.sanitizeUrlPart(name), UrlUtil.sanitizeUrlPart(value), resourceTypeMsg); // last param is pre-sanitized
throw new InvalidRequestException(Msg.code(2016) + msg);
}

View File

@ -26,8 +26,6 @@ import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.BaseStorageDao;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderToken;
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
@ -1149,7 +1147,7 @@ public class QueryStack {
private Condition createPredicateSource(@Nullable DbColumn theSourceJoinColumn, List<? extends IQueryParameterType> theList) {
if (myDaoConfig.getStoreMetaSourceInformation() == DaoConfig.StoreMetaSourceInformationEnum.NONE) {
String msg = myFhirContext.getLocalizer().getMessage(LegacySearchBuilder.class, "sourceParamDisabled");
String msg = myFhirContext.getLocalizer().getMessage(QueryStack.class, "sourceParamDisabled");
throw new InvalidRequestException(Msg.code(1216) + msg);
}
@ -1321,13 +1319,12 @@ public class QueryStack {
if (!tokenTextIndexingEnabled) {
String msg;
if (myModelConfig.isSuppressStringIndexingInTokens()) {
msg = myFhirContext.getLocalizer().getMessage(PredicateBuilderToken.class, "textModifierDisabledForServer");
msg = myFhirContext.getLocalizer().getMessage(QueryStack.class, "textModifierDisabledForServer");
} else {
msg = myFhirContext.getLocalizer().getMessage(PredicateBuilderToken.class, "textModifierDisabledForSearchParam");
msg = myFhirContext.getLocalizer().getMessage(QueryStack.class, "textModifierDisabledForSearchParam");
}
throw new MethodNotAllowedException(Msg.code(1219) + msg);
}
return createPredicateString(theSourceJoinColumn, theResourceName, theSpnamePrefix, theSearchParam, theList, null, theRequestPartitionId, theSqlBuilder);
}

View File

@ -146,10 +146,11 @@ public class CoordsPredicateBuilder extends BaseSearchParamPredicateBuilder {
BinaryCondition.greaterThanOrEq(myColumnLongitude, generatePlaceholder(theBox.bottomRight().longitude())),
BinaryCondition.lessThanOrEq(myColumnLongitude, generatePlaceholder(theBox.topLeft().longitude()))
);
}
} else {
return ComboCondition.and(
BinaryCondition.greaterThanOrEq(myColumnLongitude, generatePlaceholder(theBox.topLeft().longitude())),
BinaryCondition.lessThanOrEq(myColumnLongitude, generatePlaceholder(theBox.bottomRight().longitude()))
);
}
}
}

View File

@ -23,7 +23,6 @@ package ca.uhn.fhir.jpa.search.builder.predicate;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
import ca.uhn.fhir.jpa.dao.predicate.SearchFuzzUtil;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
@ -105,7 +104,7 @@ public class NumberPredicateBuilder extends BaseSearchParamPredicateBuilder {
break;
default:
String paramValue = theActualParam.getValueAsQueryToken(theFhirContext);
String msg = theIndexTable.getFhirContext().getLocalizer().getMessage(LegacySearchBuilder.class, theInvalidValueKey, operation, paramValue);
String msg = theIndexTable.getFhirContext().getLocalizer().getMessage(NumberPredicateBuilder.class, theInvalidValueKey, operation, paramValue);
throw new InvalidRequestException(Msg.code(1235) + msg);
}

View File

@ -37,10 +37,10 @@ import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IDao;
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
import ca.uhn.fhir.jpa.dao.BaseStorageDao;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference;
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
import ca.uhn.fhir.jpa.search.builder.QueryStack;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
@ -672,20 +672,19 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder {
@Nonnull
private InvalidRequestException newInvalidTargetTypeForChainException(String theResourceName, String theParamName, String theTypeValue) {
String searchParamName = theResourceName + ":" + theParamName;
String msg = getFhirContext().getLocalizer().getMessage(PredicateBuilderReference.class, "invalidTargetTypeForChain", theTypeValue, searchParamName);
String msg = getFhirContext().getLocalizer().getMessage(ResourceLinkPredicateBuilder.class, "invalidTargetTypeForChain", theTypeValue, searchParamName);
return new InvalidRequestException(msg);
}
private IQueryParameterType toParameterType(RuntimeSearchParam theParam, String theQualifier, String theValueAsQueryToken) {
IQueryParameterType qp = toParameterType(theParam);
qp.setValueAsQueryToken(getFhirContext(), theParam.getName(), theQualifier, theValueAsQueryToken);
return qp;
}
@Nonnull
private InvalidRequestException newInvalidResourceTypeException(String theResourceType) {
String msg = getFhirContext().getLocalizer().getMessageSanitized(PredicateBuilderReference.class, "invalidResourceType", theResourceType);
String msg = getFhirContext().getLocalizer().getMessageSanitized(SearchCoordinatorSvcImpl.class, "invalidResourceType", theResourceType);
throw new InvalidRequestException(Msg.code(1250) + msg);
}

View File

@ -32,7 +32,6 @@ import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
@ -49,9 +48,7 @@ import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.TokenParamModifier;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.FhirVersionIndependentConcept;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.collect.Sets;
import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.Condition;
@ -340,11 +337,11 @@ public class TokenPredicateBuilder extends BaseSearchParamPredicateBuilder {
String systemDesc = defaultIfBlank(theSystem, "(missing)");
String codeDesc = defaultIfBlank(theCode, "(missing)");
if (isBlank(theCode)) {
String msg = getFhirContext().getLocalizer().getMessage(LegacySearchBuilder.class, "invalidCodeMissingSystem", theParamName, systemDesc, codeDesc);
String msg = getFhirContext().getLocalizer().getMessage(TokenPredicateBuilder.class, "invalidCodeMissingSystem", theParamName, systemDesc, codeDesc);
throw new InvalidRequestException(Msg.code(1239) + msg);
}
if (isBlank(theSystem)) {
String msg = getFhirContext().getLocalizer().getMessage(LegacySearchBuilder.class, "invalidCodeMissingCode", theParamName, systemDesc, codeDesc);
String msg = getFhirContext().getLocalizer().getMessage(TokenPredicateBuilder.class, "invalidCodeMissingCode", theParamName, systemDesc, codeDesc);
throw new InvalidRequestException(Msg.code(1240) + msg);
}
}

View File

@ -1,53 +0,0 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class LegacySearchBuilderTest {
@Test
public void testIncludeIterator() {
BaseHapiFhirDao<?> mockDao = mock(BaseHapiFhirDao.class);
LegacySearchBuilder searchBuilder = new LegacySearchBuilder(mockDao, null, null);
searchBuilder.setDaoConfigForUnitTest(new DaoConfig());
searchBuilder.setParamsForUnitTest(new SearchParameterMap());
EntityManager mockEntityManager = mock(EntityManager.class);
searchBuilder.setEntityManagerForUnitTest(mockEntityManager);
Set<ResourcePersistentId> pidSet = new HashSet<>();
pidSet.add(new ResourcePersistentId(1L));
pidSet.add(new ResourcePersistentId(2L));
TypedQuery mockQuery = mock(TypedQuery.class);
when(mockEntityManager.createQuery(any(), any())).thenReturn(mockQuery);
List<Long> resultList = new ArrayList<>();
Long link = 1L;
ResourceTable target = new ResourceTable();
target.setId(1L);
resultList.add(link);
when(mockQuery.getResultList()).thenReturn(resultList);
LegacySearchBuilder.IncludesIterator includesIterator = searchBuilder.new IncludesIterator(pidSet, null);
// hasNext() should return false if the pid added was already on our list going in.
assertFalse(includesIterator.hasNext());
}
}

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.jpa.dao.predicate;
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilderTest;
import ca.uhn.fhir.jpa.dao.predicate.SearchFuzzUtil;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
@ -14,7 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.hamcrest.MatcherAssert.assertThat;
public class SearchFuzzUtilTest {
private static final Logger ourLog = LoggerFactory.getLogger(LegacySearchBuilderTest.class);
private static final Logger ourLog = LoggerFactory.getLogger(SearchFuzzUtilTest.class);
@Test
public void testCalculateMultiplierEqualNoDecimal() {

View File

@ -1,89 +0,0 @@
package ca.uhn.fhir.jpa.dao.predicate;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.util.CoordCalculator;
import ca.uhn.fhir.jpa.util.CoordCalculatorTest;
import org.hibernate.search.engine.spatial.GeoBoundingBox;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.junit.jupiter.MockitoExtension;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Predicate;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class PredicateBuilderCoordsTest {
PredicateBuilderCoords myPredicateBuilderCoords;
private LegacySearchBuilder mySearchBuilder;
private CriteriaBuilder myBuilder;
private From myFrom;
@BeforeEach
public void before() {
mySearchBuilder = mock(LegacySearchBuilder.class);
myBuilder = mock(CriteriaBuilder.class);
myFrom = mock(From.class);
myPredicateBuilderCoords = new PredicateBuilderCoords(mySearchBuilder);
}
@Test
public void testLongitudePredicateFromBox() {
GeoBoundingBox box = CoordCalculator.getBox(CoordCalculatorTest.LATITUDE_CHIN, CoordCalculatorTest.LONGITUDE_CHIN, CoordCalculatorTest.DISTANCE_TAVEUNI);
assertThat(box.bottomRight().longitude(), greaterThan(box.topLeft().longitude()));
ArgumentCaptor<Predicate> andLeft = ArgumentCaptor.forClass(Predicate.class);
ArgumentCaptor<Predicate> andRight = ArgumentCaptor.forClass(Predicate.class);
ArgumentCaptor<Double> gteValue = ArgumentCaptor.forClass(Double.class);
ArgumentCaptor<Double> lteValue = ArgumentCaptor.forClass(Double.class);
Predicate gte = mock(Predicate.class);
Predicate lte = mock(Predicate.class);
when(myBuilder.greaterThanOrEqualTo(any(), gteValue.capture())).thenReturn(gte);
when(myBuilder.lessThanOrEqualTo(any(),lteValue.capture())).thenReturn(lte);
myPredicateBuilderCoords.longitudePredicateFromBox(myBuilder, myFrom, box);
verify(myBuilder).and(andLeft.capture(), andRight.capture());
assertEquals(andLeft.getValue(), gte);
assertEquals(andRight.getValue(), lte);
assertEquals(gteValue.getValue(), box.topLeft().longitude());
assertEquals(lteValue.getValue(), box.bottomRight().longitude());
}
@Test
public void testAntiMeridianLongitudePredicateFromBox() {
GeoBoundingBox box = CoordCalculator.getBox(CoordCalculatorTest.LATITUDE_TAVEUNI, CoordCalculatorTest.LONGITIDE_TAVEUNI, CoordCalculatorTest.DISTANCE_TAVEUNI);
assertThat(box.bottomRight().longitude(), lessThan(box.topLeft().longitude()));
assertTrue(box.bottomRight().longitude() < box.topLeft().longitude());
ArgumentCaptor<Predicate> orLeft = ArgumentCaptor.forClass(Predicate.class);
ArgumentCaptor<Predicate> orRight = ArgumentCaptor.forClass(Predicate.class);
ArgumentCaptor<Double> gteValue = ArgumentCaptor.forClass(Double.class);
ArgumentCaptor<Double> lteValue = ArgumentCaptor.forClass(Double.class);
Predicate gte = mock(Predicate.class);
Predicate lte = mock(Predicate.class);
when(myBuilder.greaterThanOrEqualTo(any(), gteValue.capture())).thenReturn(gte);
when(myBuilder.lessThanOrEqualTo(any(),lteValue.capture())).thenReturn(lte);
myPredicateBuilderCoords.longitudePredicateFromBox(myBuilder, myFrom, box);
verify(myBuilder).or(orLeft.capture(), orRight.capture());
assertEquals(orLeft.getValue(), gte);
assertEquals(orRight.getValue(), lte);
assertEquals(gteValue.getValue(), box.bottomRight().longitude());
assertEquals(lteValue.getValue(), box.topLeft().longitude());
}
}

View File

@ -67,7 +67,6 @@ public class ResourceProviderHasParamR4Test extends BaseResourceProviderR4Test {
myDaoConfig.setAllowMultipleDelete(true);
myClient.registerInterceptor(myCapturingInterceptor);
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
//myDaoConfig.setUseLegacySearchBuilder(true);
}
@BeforeEach

View File

@ -5,8 +5,8 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IResultIterator;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
import ca.uhn.fhir.jpa.search.builder.SearchBuilder;
import ca.uhn.fhir.jpa.util.BaseIterator;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
@ -32,7 +32,7 @@ public class BaseSearchSvc {
@Mock
protected PlatformTransactionManager myTxManager;
@Mock
protected LegacySearchBuilder mySearchBuilder;
protected SearchBuilder mySearchBuilder;
@Mock
protected IFhirResourceDao<?> myCallingDao;

View File

@ -505,7 +505,7 @@ public class DaoConfig {
* @since 5.3.0
*/
public boolean isUseLegacySearchBuilder() {
return myUseLegacySearchBuilder;
return false;
}
/**
@ -515,9 +515,10 @@ public class DaoConfig {
* <p>Note that this method will be removed in HAPI FHIR 5.4.0</p>
*
* @since 5.3.0
* @deprecated in 6.1.0, this toggle will be removed in 6.2.0 as the Legacy Search Builder has been removed.
*/
public void setUseLegacySearchBuilder(boolean theUseLegacySearchBuilder) {
myUseLegacySearchBuilder = theUseLegacySearchBuilder;
//Nop
}
/**