001/*-
002 * #%L
003 * HAPI FHIR JPA Server
004 * %%
005 * Copyright (C) 2014 - 2024 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.api.svc.IIdHelperService;
024import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
025import ca.uhn.fhir.jpa.model.dao.JpaPid;
026import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
027import ca.uhn.fhir.jpa.util.QueryParameterUtils;
028import ca.uhn.fhir.model.api.IQueryParameterType;
029import ca.uhn.fhir.rest.param.TokenParam;
030import ca.uhn.fhir.rest.param.TokenParamModifier;
031import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
032import com.healthmarketscience.sqlbuilder.Condition;
033import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
034import jakarta.annotation.Nullable;
035import org.hl7.fhir.r4.model.IdType;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038import org.springframework.beans.factory.annotation.Autowired;
039
040import java.util.HashSet;
041import java.util.List;
042import java.util.Set;
043
044import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
045import static org.apache.commons.lang3.StringUtils.isNotBlank;
046
047public class ResourceIdPredicateBuilder extends BasePredicateBuilder {
048        private static final Logger ourLog = LoggerFactory.getLogger(ResourceIdPredicateBuilder.class);
049
050        @Autowired
051        private IIdHelperService<JpaPid> myIdHelperService;
052
053        /**
054         * Constructor
055         */
056        public ResourceIdPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder) {
057                super(theSearchSqlBuilder);
058        }
059
060        @Nullable
061        public Condition createPredicateResourceId(
062                        @Nullable DbColumn theSourceJoinColumn,
063                        String theResourceName,
064                        List<List<IQueryParameterType>> theValues,
065                        SearchFilterParser.CompareOperation theOperation,
066                        RequestPartitionId theRequestPartitionId) {
067
068                Set<JpaPid> allOrPids = null;
069                SearchFilterParser.CompareOperation defaultOperation = SearchFilterParser.CompareOperation.eq;
070
071                boolean allIdsAreForcedIds = true;
072                for (List<? extends IQueryParameterType> nextValue : theValues) {
073                        Set<JpaPid> orPids = new HashSet<>();
074                        boolean haveValue = false;
075                        for (IQueryParameterType next : nextValue) {
076                                String value = next.getValueAsQueryToken(getFhirContext());
077                                if (value != null && value.startsWith("|")) {
078                                        value = value.substring(1);
079                                }
080
081                                IdType valueAsId = new IdType(value);
082                                if (isNotBlank(value)) {
083                                        if (!myIdHelperService.idRequiresForcedId(valueAsId.getIdPart()) && allIdsAreForcedIds) {
084                                                allIdsAreForcedIds = false;
085                                        }
086                                        haveValue = true;
087                                        try {
088                                                boolean excludeDeleted = true;
089                                                JpaPid pid = myIdHelperService.resolveResourcePersistentIds(
090                                                                theRequestPartitionId, theResourceName, valueAsId.getIdPart(), excludeDeleted);
091                                                orPids.add(pid);
092                                        } catch (ResourceNotFoundException e) {
093                                                // This is not an error in a search, it just results in no matches
094                                                ourLog.debug("Resource ID {} was requested but does not exist", valueAsId.getIdPart());
095                                        }
096                                }
097
098                                if (next instanceof TokenParam) {
099                                        if (((TokenParam) next).getModifier() == TokenParamModifier.NOT) {
100                                                defaultOperation = SearchFilterParser.CompareOperation.ne;
101                                        }
102                                }
103                        }
104                        if (haveValue) {
105                                if (allOrPids == null) {
106                                        allOrPids = orPids;
107                                } else {
108                                        allOrPids.retainAll(orPids);
109                                }
110                        }
111                }
112
113                if (allOrPids != null && allOrPids.isEmpty()) {
114
115                        setMatchNothing();
116
117                } else if (allOrPids != null) {
118
119                        SearchFilterParser.CompareOperation operation = defaultIfNull(theOperation, defaultOperation);
120                        assert operation == SearchFilterParser.CompareOperation.eq
121                                        || operation == SearchFilterParser.CompareOperation.ne;
122
123                        List<Long> resourceIds = JpaPid.toLongList(allOrPids);
124                        if (theSourceJoinColumn == null) {
125                                BaseJoiningPredicateBuilder queryRootTable = super.getOrCreateQueryRootTable(!allIdsAreForcedIds);
126                                Condition predicate;
127                                switch (operation) {
128                                        default:
129                                        case eq:
130                                                predicate = queryRootTable.createPredicateResourceIds(false, resourceIds);
131                                                return queryRootTable.combineWithRequestPartitionIdPredicate(theRequestPartitionId, predicate);
132                                        case ne:
133                                                predicate = queryRootTable.createPredicateResourceIds(true, resourceIds);
134                                                return queryRootTable.combineWithRequestPartitionIdPredicate(theRequestPartitionId, predicate);
135                                }
136                        } else {
137                                return QueryParameterUtils.toEqualToOrInPredicate(
138                                                theSourceJoinColumn,
139                                                generatePlaceholders(resourceIds),
140                                                operation == SearchFilterParser.CompareOperation.ne);
141                        }
142                }
143
144                return null;
145        }
146}