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.dao.data.IResourceIndexedSearchParamUriDao;
028import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
029import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
030import ca.uhn.fhir.model.api.IQueryParameterType;
031import ca.uhn.fhir.rest.param.UriParam;
032import ca.uhn.fhir.rest.param.UriParamQualifierEnum;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035import org.springframework.beans.factory.annotation.Autowired;
036import org.springframework.context.annotation.Scope;
037import org.springframework.stereotype.Component;
038
039import javax.persistence.criteria.From;
040import javax.persistence.criteria.Predicate;
041import java.util.ArrayList;
042import java.util.Collection;
043import java.util.List;
044
045@Component
046@Scope("prototype")
047public class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuilder {
048        private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderUri.class);
049        @Autowired
050        private IResourceIndexedSearchParamUriDao myResourceIndexedSearchParamUriDao;
051
052        public PredicateBuilderUri(LegacySearchBuilder theSearchBuilder) {
053                super(theSearchBuilder);
054        }
055
056        @Override
057        public Predicate addPredicate(String theResourceName,
058                                                                                        RuntimeSearchParam theSearchParam,
059                                                                                        List<? extends IQueryParameterType> theList,
060                                                                                        SearchFilterParser.CompareOperation operation,
061                                                                                        RequestPartitionId theRequestPartitionId) {
062
063                String paramName = theSearchParam.getName();
064                From<?, ResourceIndexedSearchParamUri> join = myQueryStack.createJoin(SearchBuilderJoinEnum.URI, paramName);
065
066                if (theList.get(0).getMissing() != null) {
067                        addPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), join, theRequestPartitionId);
068                        return null;
069                }
070
071                List<Predicate> codePredicates = new ArrayList<>();
072                addPartitionIdPredicate(theRequestPartitionId, join, codePredicates);
073
074                for (IQueryParameterType nextOr : theList) {
075
076                        if (nextOr instanceof UriParam) {
077                                UriParam param = (UriParam) nextOr;
078
079                                String value = param.getValue();
080                                if (value == null) {
081                                        continue;
082                                }
083
084                                if (param.getQualifier() == UriParamQualifierEnum.ABOVE) {
085
086                                        /*
087                                         * :above is an inefficient query- It means that the user is supplying a more specific URL (say
088                                         * http://example.com/foo/bar/baz) and that we should match on any URLs that are less
089                                         * specific but otherwise the same. For example http://example.com and http://example.com/foo would both
090                                         * match.
091                                         *
092                                         * We do this by querying the DB for all candidate URIs and then manually checking each one. This isn't
093                                         * very efficient, but this is also probably not a very common type of query to do.
094                                         *
095                                         * If we ever need to make this more efficient, lucene could certainly be used as an optimization.
096                                         */
097                                        ourLog.info("Searching for candidate URI:above parameters for Resource[{}] param[{}]", myResourceName, paramName);
098                                        Collection<String> candidates = myResourceIndexedSearchParamUriDao.findAllByResourceTypeAndParamName(myResourceName, paramName);
099                                        List<String> toFind = new ArrayList<>();
100                                        for (String next : candidates) {
101                                                if (value.length() >= next.length()) {
102                                                        if (value.startsWith(next)) {
103                                                                toFind.add(next);
104                                                        }
105                                                }
106                                        }
107
108                                        if (toFind.isEmpty()) {
109                                                continue;
110                                        }
111
112                                        Predicate uriPredicate = join.get("myUri").as(String.class).in(toFind);
113                                        Predicate hashAndUriPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, join, uriPredicate, theRequestPartitionId);
114                                        codePredicates.add(hashAndUriPredicate);
115
116                                } else if (param.getQualifier() == UriParamQualifierEnum.BELOW) {
117
118                                        Predicate uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value));
119                                        Predicate hashAndUriPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, join, uriPredicate, theRequestPartitionId);
120                                        codePredicates.add(hashAndUriPredicate);
121
122                                } else {
123                                        if (myDontUseHashesForSearch) {
124                                                Predicate predicate = myCriteriaBuilder.equal(join.get("myUri").as(String.class), value);
125                                                codePredicates.add(predicate);
126                                        } else {
127
128                                                Predicate uriPredicate = null;
129                                                if (operation == null || operation == SearchFilterParser.CompareOperation.eq) {
130                                                        long hashUri = ResourceIndexedSearchParamUri.calculateHashUri(getPartitionSettings(), theRequestPartitionId, theResourceName, paramName, value);
131                                                        Predicate hashPredicate = myCriteriaBuilder.equal(join.get("myHashUri"), hashUri);
132                                                        codePredicates.add(hashPredicate);
133                                                } else if (operation == SearchFilterParser.CompareOperation.ne) {
134                                                        uriPredicate = myCriteriaBuilder.notEqual(join.get("myUri").as(String.class), value);
135                                                } else if (operation == SearchFilterParser.CompareOperation.co) {
136                                                        uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createLeftAndRightMatchLikeExpression(value));
137                                                } else if (operation == SearchFilterParser.CompareOperation.gt) {
138                                                        uriPredicate = myCriteriaBuilder.greaterThan(join.get("myUri").as(String.class), value);
139                                                } else if (operation == SearchFilterParser.CompareOperation.lt) {
140                                                        uriPredicate = myCriteriaBuilder.lessThan(join.get("myUri").as(String.class), value);
141                                                } else if (operation == SearchFilterParser.CompareOperation.ge) {
142                                                        uriPredicate = myCriteriaBuilder.greaterThanOrEqualTo(join.get("myUri").as(String.class), value);
143                                                } else if (operation == SearchFilterParser.CompareOperation.le) {
144                                                        uriPredicate = myCriteriaBuilder.lessThanOrEqualTo(join.get("myUri").as(String.class), value);
145                                                } else if (operation == SearchFilterParser.CompareOperation.sw) {
146                                                        uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value));
147                                                } else if (operation == SearchFilterParser.CompareOperation.ew) {
148                                                        uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createRightMatchLikeExpression(value));
149                                                } else {
150                                                        throw new IllegalArgumentException(Msg.code(1070) + String.format("Unsupported operator specified in _filter clause, %s",
151                                                                operation.toString()));
152                                                }
153
154                                                if (uriPredicate != null) {
155                                                        long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, theResourceName, paramName);
156                                                        Predicate hashIdentityPredicate = myCriteriaBuilder.equal(join.get("myHashIdentity"), hashIdentity);
157                                                        codePredicates.add(myCriteriaBuilder.and(hashIdentityPredicate, uriPredicate));
158                                                }
159                                        }
160                                }
161
162                        } else {
163                                throw new IllegalArgumentException(Msg.code(1071) + "Invalid URI type: " + nextOr.getClass());
164                        }
165
166                }
167
168                /*
169                 * If we haven't found any of the requested URIs in the candidates, then we'll
170                 * just add a predicate that can never match
171                 */
172                if (codePredicates.isEmpty()) {
173                        Predicate predicate = myCriteriaBuilder.isNull(join.get("myMissing").as(String.class));
174                        myQueryStack.addPredicateWithImplicitTypeSelection(predicate);
175                        return null;
176                }
177
178                Predicate orPredicate = myCriteriaBuilder.or(toArray(codePredicates));
179
180                Predicate outerPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName,
181                        paramName,
182                        join,
183                        orPredicate,
184                theRequestPartitionId);
185                myQueryStack.addPredicateWithImplicitTypeSelection(outerPredicate);
186                return outerPredicate;
187        }
188
189}