
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}