
001package ca.uhn.fhir.jpa.dao.predicate; 002 003/*- 004 * #%L 005 * HAPI FHIR JPA Server 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.context.RuntimeSearchParam; 025import ca.uhn.fhir.interceptor.model.RequestPartitionId; 026import ca.uhn.fhir.jpa.dao.LegacySearchBuilder; 027import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; 028import ca.uhn.fhir.model.api.IPrimitiveDatatype; 029import ca.uhn.fhir.model.api.IQueryParameterType; 030import ca.uhn.fhir.rest.param.StringParam; 031import ca.uhn.fhir.rest.param.TokenParam; 032import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 033import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; 034import ca.uhn.fhir.util.StringUtil; 035import org.springframework.context.annotation.Scope; 036import org.springframework.stereotype.Component; 037 038import javax.persistence.criteria.CriteriaBuilder; 039import javax.persistence.criteria.From; 040import javax.persistence.criteria.Predicate; 041import java.util.ArrayList; 042import java.util.List; 043 044@Component 045@Scope("prototype") 046public class PredicateBuilderString extends BasePredicateBuilder implements IPredicateBuilder { 047 public PredicateBuilderString(LegacySearchBuilder theSearchBuilder) { 048 super(theSearchBuilder); 049 } 050 051 @Override 052 public Predicate addPredicate(String theResourceName, 053 RuntimeSearchParam theSearchParam, 054 List<? extends IQueryParameterType> theList, 055 SearchFilterParser.CompareOperation theOperation, 056 RequestPartitionId theRequestPartitionId) { 057 058 From<?, ResourceIndexedSearchParamString> join = myQueryStack.createJoin(SearchBuilderJoinEnum.STRING, theSearchParam.getName()); 059 060 if (theList.get(0).getMissing() != null) { 061 addPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), join, theRequestPartitionId); 062 return null; 063 } 064 065 List<Predicate> codePredicates = new ArrayList<>(); 066 addPartitionIdPredicate(theRequestPartitionId, join, codePredicates); 067 068 for (IQueryParameterType nextOr : theList) { 069 Predicate singleCode = createPredicateString(nextOr, theResourceName, theSearchParam, myCriteriaBuilder, join, theOperation, theRequestPartitionId); 070 codePredicates.add(singleCode); 071 } 072 073 Predicate retVal = myCriteriaBuilder.or(toArray(codePredicates)); 074 075 myQueryStack.addPredicateWithImplicitTypeSelection(retVal); 076 077 return retVal; 078 } 079 080 public Predicate createPredicateString(IQueryParameterType theParameter, 081 String theResourceName, 082 RuntimeSearchParam theSearchParam, 083 CriteriaBuilder theBuilder, 084 From<?, ResourceIndexedSearchParamString> theFrom, 085 RequestPartitionId theRequestPartitionId) { 086 return createPredicateString(theParameter, 087 theResourceName, 088 theSearchParam, 089 theBuilder, 090 theFrom, 091 null, 092 theRequestPartitionId); 093 } 094 095 private Predicate createPredicateString(IQueryParameterType theParameter, 096 String theResourceName, 097 RuntimeSearchParam theSearchParam, 098 CriteriaBuilder theBuilder, 099 From<?, ResourceIndexedSearchParamString> theFrom, 100 SearchFilterParser.CompareOperation operation, 101 RequestPartitionId theRequestPartitionId) { 102 String rawSearchTerm; 103 String paramName = theSearchParam.getName(); 104 if (theParameter instanceof TokenParam) { 105 TokenParam id = (TokenParam) theParameter; 106 if (!id.isText()) { 107 throw new IllegalStateException(Msg.code(1047) + "Trying to process a text search on a non-text token parameter"); 108 } 109 rawSearchTerm = id.getValue(); 110 } else if (theParameter instanceof StringParam) { 111 StringParam id = (StringParam) theParameter; 112 rawSearchTerm = id.getValue(); 113 if (id.isContains()) { 114 if (!myDaoConfig.isAllowContainsSearches()) { 115 throw new MethodNotAllowedException(Msg.code(1048) + ":contains modifier is disabled on this server"); 116 } 117 } else { 118 rawSearchTerm = theSearchParam.encode(rawSearchTerm); 119 } 120 } else if (theParameter instanceof IPrimitiveDatatype<?>) { 121 IPrimitiveDatatype<?> id = (IPrimitiveDatatype<?>) theParameter; 122 rawSearchTerm = id.getValueAsString(); 123 } else { 124 throw new IllegalArgumentException(Msg.code(1049) + "Invalid token type: " + theParameter.getClass()); 125 } 126 127 if (rawSearchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) { 128 throw new InvalidRequestException(Msg.code(1050) + "Parameter[" + paramName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed (" 129 + ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm); 130 } 131 132 if (myDontUseHashesForSearch) { 133 String likeExpression = StringUtil.normalizeStringForSearchIndexing(rawSearchTerm); 134 if (myDaoConfig.isAllowContainsSearches()) { 135 if (theParameter instanceof StringParam) { 136 if (((StringParam) theParameter).isContains()) { 137 likeExpression = createLeftAndRightMatchLikeExpression(likeExpression); 138 } else { 139 likeExpression = createLeftMatchLikeExpression(likeExpression); 140 } 141 } else { 142 likeExpression = createLeftMatchLikeExpression(likeExpression); 143 } 144 } else { 145 likeExpression = createLeftMatchLikeExpression(likeExpression); 146 } 147 148 Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression); 149 if (theParameter instanceof StringParam && ((StringParam) theParameter).isExact()) { 150 Predicate exactCode = theBuilder.equal(theFrom.get("myValueExact"), rawSearchTerm); 151 singleCode = theBuilder.and(singleCode, exactCode); 152 } 153 154 return combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId); 155 } 156 boolean exactMatch = theParameter instanceof StringParam && ((StringParam) theParameter).isExact(); 157 if (exactMatch) { 158 // Exact match 159 Long hash = ResourceIndexedSearchParamString.calculateHashExact(getPartitionSettings(), theRequestPartitionId, theResourceName, paramName, rawSearchTerm); 160 return theBuilder.equal(theFrom.get("myHashExact").as(Long.class), hash); 161 } else { 162 // Normalized Match 163 String normalizedString = StringUtil.normalizeStringForSearchIndexing(rawSearchTerm); 164 String likeExpression; 165 if ((theParameter instanceof StringParam) && 166 (((((StringParam) theParameter).isContains()) && 167 (myDaoConfig.isAllowContainsSearches())) || 168 (operation == SearchFilterParser.CompareOperation.co))) { 169 likeExpression = createLeftAndRightMatchLikeExpression(normalizedString); 170 } else if ((operation != SearchFilterParser.CompareOperation.ne) && 171 (operation != SearchFilterParser.CompareOperation.gt) && 172 (operation != SearchFilterParser.CompareOperation.lt) && 173 (operation != SearchFilterParser.CompareOperation.ge) && 174 (operation != SearchFilterParser.CompareOperation.le)) { 175 if (operation == SearchFilterParser.CompareOperation.ew) { 176 likeExpression = createRightMatchLikeExpression(normalizedString); 177 } else { 178 likeExpression = createLeftMatchLikeExpression(normalizedString); 179 } 180 } else { 181 likeExpression = normalizedString; 182 } 183 184 Predicate predicate; 185 if ((operation == null) || 186 (operation == SearchFilterParser.CompareOperation.sw)) { 187 Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(getPartitionSettings(), theRequestPartitionId, myDaoConfig.getModelConfig(), theResourceName, paramName, normalizedString); 188 Predicate hashCode = theBuilder.equal(theFrom.get("myHashNormalizedPrefix").as(Long.class), hash); 189 Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression); 190 predicate = theBuilder.and(hashCode, singleCode); 191 } else if ((operation == SearchFilterParser.CompareOperation.ew) || 192 (operation == SearchFilterParser.CompareOperation.co)) { 193 Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression); 194 predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId); 195 } else if (operation == SearchFilterParser.CompareOperation.eq) { 196 Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(getPartitionSettings(), theRequestPartitionId, myDaoConfig.getModelConfig(), theResourceName, paramName, normalizedString); 197 Predicate hashCode = theBuilder.equal(theFrom.get("myHashNormalizedPrefix").as(Long.class), hash); 198 Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), normalizedString); 199 predicate = theBuilder.and(hashCode, singleCode); 200 } else if (operation == SearchFilterParser.CompareOperation.ne) { 201 Predicate singleCode = theBuilder.notEqual(theFrom.get("myValueNormalized").as(String.class), likeExpression); 202 predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId); 203 } else if (operation == SearchFilterParser.CompareOperation.gt) { 204 Predicate singleCode = theBuilder.greaterThan(theFrom.get("myValueNormalized").as(String.class), likeExpression); 205 predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId); 206 } else if (operation == SearchFilterParser.CompareOperation.lt) { 207 Predicate singleCode = theBuilder.lessThan(theFrom.get("myValueNormalized").as(String.class), likeExpression); 208 predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId); 209 } else if (operation == SearchFilterParser.CompareOperation.ge) { 210 Predicate singleCode = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueNormalized").as(String.class), likeExpression); 211 predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId); 212 } else if (operation == SearchFilterParser.CompareOperation.le) { 213 Predicate singleCode = theBuilder.lessThanOrEqualTo(theFrom.get("myValueNormalized").as(String.class), likeExpression); 214 predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId); 215 } else { 216 throw new IllegalArgumentException(Msg.code(1051) + "Don't yet know how to handle operation " + operation + " on a string"); 217 } 218 219 return predicate; 220 } 221 } 222}