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}