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}