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.interceptor.model.RequestPartitionId;
025import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
026import ca.uhn.fhir.jpa.model.entity.ResourceTag;
027import ca.uhn.fhir.jpa.model.entity.TagDefinition;
028import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
029import ca.uhn.fhir.model.api.IQueryParameterType;
030import ca.uhn.fhir.rest.api.Constants;
031import ca.uhn.fhir.rest.param.TokenParam;
032import ca.uhn.fhir.rest.param.TokenParamModifier;
033import ca.uhn.fhir.rest.param.UriParam;
034import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
035import com.google.common.collect.Lists;
036import org.apache.commons.lang3.tuple.Pair;
037import org.slf4j.Logger;
038import org.slf4j.LoggerFactory;
039import org.springframework.context.annotation.Scope;
040import org.springframework.stereotype.Component;
041
042import javax.persistence.criteria.CriteriaBuilder;
043import javax.persistence.criteria.From;
044import javax.persistence.criteria.Path;
045import javax.persistence.criteria.Predicate;
046import javax.persistence.criteria.Root;
047import javax.persistence.criteria.Subquery;
048import java.util.List;
049
050import static org.apache.commons.lang3.StringUtils.isNotBlank;
051
052@Component
053@Scope("prototype")
054public class PredicateBuilderTag extends BasePredicateBuilder {
055        private static final Logger ourLog = LoggerFactory.getLogger(PredicateBuilderTag.class);
056
057        public PredicateBuilderTag(LegacySearchBuilder theSearchBuilder) {
058                super(theSearchBuilder);
059        }
060
061        void addPredicateTag(List<List<IQueryParameterType>> theList, String theParamName, RequestPartitionId theRequestPartitionId) {
062                TagTypeEnum tagType;
063                if (Constants.PARAM_TAG.equals(theParamName)) {
064                        tagType = TagTypeEnum.TAG;
065                } else if (Constants.PARAM_PROFILE.equals(theParamName)) {
066                        tagType = TagTypeEnum.PROFILE;
067                } else if (Constants.PARAM_SECURITY.equals(theParamName)) {
068                        tagType = TagTypeEnum.SECURITY_LABEL;
069                } else {
070                        throw new IllegalArgumentException(Msg.code(1030) + "Param name: " + theParamName); // shouldn't happen
071                }
072
073                List<Pair<String, String>> notTags = Lists.newArrayList();
074                for (List<? extends IQueryParameterType> nextAndParams : theList) {
075                        for (IQueryParameterType nextOrParams : nextAndParams) {
076                                if (nextOrParams instanceof TokenParam) {
077                                        TokenParam param = (TokenParam) nextOrParams;
078                                        if (param.getModifier() == TokenParamModifier.NOT) {
079                                                if (isNotBlank(param.getSystem()) || isNotBlank(param.getValue())) {
080                                                        notTags.add(Pair.of(param.getSystem(), param.getValue()));
081                                                }
082                                        }
083                                }
084                        }
085                }
086
087                for (List<? extends IQueryParameterType> nextAndParams : theList) {
088                        boolean haveTags = false;
089                        for (IQueryParameterType nextParamUncasted : nextAndParams) {
090                                if (nextParamUncasted instanceof TokenParam) {
091                                        TokenParam nextParam = (TokenParam) nextParamUncasted;
092                                        if (isNotBlank(nextParam.getValue())) {
093                                                haveTags = true;
094                                        } else if (isNotBlank(nextParam.getSystem())) {
095                                                throw new InvalidRequestException(Msg.code(1031) + "Invalid " + theParamName + " parameter (must supply a value/code and not just a system): " + nextParam.getValueAsQueryToken(myContext));
096                                        }
097                                } else {
098                                        UriParam nextParam = (UriParam) nextParamUncasted;
099                                        if (isNotBlank(nextParam.getValue())) {
100                                                haveTags = true;
101                                        }
102                                }
103                        }
104                        if (!haveTags) {
105                                continue;
106                        }
107
108                        boolean paramInverted = false;
109                        List<Pair<String, String>> tokens = Lists.newArrayList();
110                        for (IQueryParameterType nextOrParams : nextAndParams) {
111                                String code;
112                                String system;
113                                if (nextOrParams instanceof TokenParam) {
114                                        TokenParam nextParam = (TokenParam) nextOrParams;
115                                        code = nextParam.getValue();
116                                        system = nextParam.getSystem();
117                                        if (nextParam.getModifier() == TokenParamModifier.NOT) {
118                                                paramInverted = true;
119                                        }
120                                } else {
121                                        UriParam nextParam = (UriParam) nextOrParams;
122                                        code = nextParam.getValue();
123                                        system = null;
124                                }
125
126                                if (isNotBlank(code)) {
127                                        tokens.add(Pair.of(system, code));
128                                }
129                        }
130
131                        if (tokens.isEmpty()) {
132                                continue;
133                        }
134
135                        if (paramInverted) {
136                                ourLog.debug("Searching for _tag:not");
137
138                                Subquery<Long> subQ = myQueryStack.subqueryForTagNegation();
139                                Root<ResourceTag> subQfrom = subQ.from(ResourceTag.class);
140                                subQ.select(subQfrom.get("myResourceId").as(Long.class));
141
142                                myQueryStack.addPredicate(
143                                        myCriteriaBuilder.not(
144                                                myCriteriaBuilder.in(
145                                                        myQueryStack.get("myId")
146                                                ).value(subQ)
147                                        )
148                                );
149
150                                Subquery<Long> defJoin = subQ.subquery(Long.class);
151                                Root<TagDefinition> defJoinFrom = defJoin.from(TagDefinition.class);
152                                defJoin.select(defJoinFrom.get("myId").as(Long.class));
153
154                                subQ.where(subQfrom.get("myTagId").as(Long.class).in(defJoin));
155
156                                Predicate tagListPredicate = createPredicateTagList(defJoinFrom, myCriteriaBuilder, tagType, tokens);
157                                defJoin.where(tagListPredicate);
158
159                                continue;
160
161                        }
162
163                        From<?, ResourceTag> tagJoin = myQueryStack.createJoin(SearchBuilderJoinEnum.RESOURCE_TAGS, null);
164                        From<ResourceTag, TagDefinition> defJoin = tagJoin.join("myTag");
165
166                        Predicate tagListPredicate = createPredicateTagList(defJoin, myCriteriaBuilder, tagType, tokens);
167                        List<Predicate> predicates = Lists.newArrayList(tagListPredicate);
168
169                        if (theRequestPartitionId != null) {
170                                addPartitionIdPredicate(theRequestPartitionId, tagJoin, predicates);
171                        }
172
173                        myQueryStack.addPredicates(predicates);
174
175                }
176
177        }
178
179        private Predicate createPredicateTagList(Path<TagDefinition> theDefJoin, CriteriaBuilder theBuilder, TagTypeEnum theTagType, List<Pair<String, String>> theTokens) {
180                Predicate typePredicate = theBuilder.equal(theDefJoin.get("myTagType"), theTagType);
181
182                List<Predicate> orPredicates = Lists.newArrayList();
183                for (Pair<String, String> next : theTokens) {
184                        Predicate codePredicate = theBuilder.equal(theDefJoin.get("myCode"), next.getRight());
185                        if (isNotBlank(next.getLeft())) {
186                                Predicate systemPredicate = theBuilder.equal(theDefJoin.get("mySystem"), next.getLeft());
187                                orPredicates.add(theBuilder.and(typePredicate, systemPredicate, codePredicate));
188                        } else {
189                                orPredicates.add(theBuilder.and(typePredicate, codePredicate));
190                        }
191                }
192
193                return theBuilder.or(toArray(orPredicates));
194        }
195
196}