001/*-
002 * #%L
003 * HAPI FHIR JPA Server
004 * %%
005 * Copyright (C) 2014 - 2025 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.jpa.search.builder.predicate;
021
022import ca.uhn.fhir.interceptor.model.RequestPartitionId;
023import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
024import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
025import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
026import ca.uhn.fhir.rest.param.UriParamQualifierEnum;
027import com.google.common.collect.Lists;
028import com.healthmarketscience.sqlbuilder.BinaryCondition;
029import com.healthmarketscience.sqlbuilder.ComboCondition;
030import com.healthmarketscience.sqlbuilder.Condition;
031import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
032import com.healthmarketscience.sqlbuilder.dbspec.basic.DbTable;
033import org.apache.commons.lang3.tuple.Triple;
034
035import java.util.List;
036import java.util.Objects;
037
038import static ca.uhn.fhir.jpa.search.builder.predicate.StringPredicateBuilder.createLeftMatchLikeExpression;
039import static org.apache.commons.lang3.StringUtils.isNotBlank;
040
041public class TagPredicateBuilder extends BaseJoiningPredicateBuilder {
042
043        private final DbColumn myColumnResId;
044        private final DbTable myTagDefinitionTable;
045        private final DbColumn myTagDefinitionColumnTagId;
046        private final DbColumn myTagDefinitionColumnTagSystem;
047        private final DbColumn myTagDefinitionColumnTagCode;
048        private final DbColumn myColumnTagId;
049        private final DbColumn myTagDefinitionColumnTagType;
050        private volatile boolean myTagDefinitionTableIsJoined = false;
051
052        public TagPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
053                super(theSearchSqlBuilder, theSearchSqlBuilder.addTable("HFJ_RES_TAG"));
054
055                myColumnResId = getTable().addColumn("RES_ID");
056                myColumnTagId = getTable().addColumn("TAG_ID");
057
058                myTagDefinitionTable = theSearchSqlBuilder.addTable("HFJ_TAG_DEF");
059                myTagDefinitionColumnTagId = myTagDefinitionTable.addColumn("TAG_ID");
060                myTagDefinitionColumnTagSystem = myTagDefinitionTable.addColumn("TAG_SYSTEM");
061                myTagDefinitionColumnTagCode = myTagDefinitionTable.addColumn("TAG_CODE");
062                myTagDefinitionColumnTagType = myTagDefinitionTable.addColumn("TAG_TYPE");
063        }
064
065        public Condition createPredicateTag(
066                        TagTypeEnum theTagType,
067                        List<Triple<String, String, String>> theTokens,
068                        String theParamName,
069                        RequestPartitionId theRequestPartitionId) {
070                if (!myTagDefinitionTableIsJoined) {
071                        synchronized (this) {
072                                if (!myTagDefinitionTableIsJoined) {
073                                        addJoin(getTable(), myTagDefinitionTable, new DbColumn[] {myColumnTagId}, new DbColumn[] {
074                                                myTagDefinitionColumnTagId
075                                        });
076                                        myTagDefinitionTableIsJoined = true;
077                                }
078                        }
079                }
080                return createPredicateTagList(theTagType, theTokens);
081        }
082
083        private Condition createPredicateTagList(TagTypeEnum theTagType, List<Triple<String, String, String>> theTokens) {
084                Condition typePredicate =
085                                BinaryCondition.equalTo(myTagDefinitionColumnTagType, generatePlaceholder(theTagType.ordinal()));
086
087                List<Condition> orPredicates = Lists.newArrayList();
088                for (Triple<String, String, String> next : theTokens) {
089                        String system = next.getLeft();
090                        String code = next.getRight();
091                        String qualifier = next.getMiddle();
092
093                        if (theTagType == TagTypeEnum.PROFILE) {
094                                system = BaseHapiFhirDao.NS_JPA_PROFILE;
095                        }
096
097                        Condition codePredicate = Objects.equals(qualifier, UriParamQualifierEnum.BELOW.getValue())
098                                        ? BinaryCondition.like(
099                                                        myTagDefinitionColumnTagCode, generatePlaceholder(createLeftMatchLikeExpression(code)))
100                                        : BinaryCondition.equalTo(myTagDefinitionColumnTagCode, generatePlaceholder(code));
101
102                        if (isNotBlank(system)) {
103                                Condition systemPredicate =
104                                                BinaryCondition.equalTo(myTagDefinitionColumnTagSystem, generatePlaceholder(system));
105                                orPredicates.add(ComboCondition.and(typePredicate, systemPredicate, codePredicate));
106                        } else {
107                                // Note: We don't have an index for this combo, which means that this may not perform
108                                // well on MySQL (and maybe others) without an added index
109                                orPredicates.add(ComboCondition.and(typePredicate, codePredicate));
110                        }
111                }
112
113                return ComboCondition.or(orPredicates.toArray(new Condition[0]));
114        }
115
116        @Override
117        public DbColumn getResourceIdColumn() {
118                return myColumnResId;
119        }
120}