
001/*- 002 * #%L 003 * HAPI FHIR JPA - Search Parameters 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.partition; 021 022import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails; 023import ca.uhn.fhir.interceptor.model.RequestPartitionId; 024import ca.uhn.fhir.jpa.model.dao.JpaPid; 025import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 026import ca.uhn.fhir.rest.api.server.RequestDetails; 027import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId; 028import jakarta.annotation.Nonnull; 029import jakarta.annotation.Nullable; 030import org.hl7.fhir.instance.model.api.IBaseResource; 031import org.hl7.fhir.instance.model.api.IIdType; 032 033import java.util.Objects; 034import java.util.Set; 035 036public interface IRequestPartitionHelperSvc { 037 038 @Nonnull 039 RequestPartitionId determineReadPartitionForRequest( 040 @Nullable RequestDetails theRequest, @Nonnull ReadPartitionIdRequestDetails theDetails); 041 042 /** 043 * Determine partition to use when performing a server operation such as $bulk-import, $bulk-export, $reindex etc. 044 * @param theRequest the request details from the context of the call 045 * @param theOperationName the explicit name of the operation 046 * @return the partition id which should be used for the operation 047 */ 048 @Nonnull 049 default RequestPartitionId determineReadPartitionForRequestForServerOperation( 050 @Nullable RequestDetails theRequest, @Nonnull String theOperationName) { 051 ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forServerOperation(theOperationName); 052 return determineReadPartitionForRequest(theRequest, details); 053 } 054 055 /** 056 * Determine partition to use when performing database reads based on a resource instance. 057 * @param theRequest the request details from the context of the call 058 * @param theId the id of the resource instance 059 * @return the partition id which should be used for the database read 060 */ 061 @Nonnull 062 default RequestPartitionId determineReadPartitionForRequestForRead( 063 @Nullable RequestDetails theRequest, @Nonnull IIdType theId) { 064 ReadPartitionIdRequestDetails details = 065 ReadPartitionIdRequestDetails.forRead(theId.getResourceType(), theId, theId.hasVersionIdPart()); 066 return determineReadPartitionForRequest(theRequest, details); 067 } 068 069 /** 070 * Determine partition to use when performing database reads against a certain resource type based on a resource instance. 071 * @param theRequest the request details from the context of the call 072 * @param theResourceType the resource type 073 * @param theId the id of the resource instance 074 * @return the partition id which should be used for the database read 075 */ 076 @Nonnull 077 default RequestPartitionId determineReadPartitionForRequestForRead( 078 @Nullable RequestDetails theRequest, @Nonnull String theResourceType, @Nonnull IIdType theId) { 079 ReadPartitionIdRequestDetails details = 080 ReadPartitionIdRequestDetails.forRead(theResourceType, theId, theId.hasVersionIdPart()); 081 return determineReadPartitionForRequest(theRequest, details); 082 } 083 084 /** 085 * Determine partition to use when performing a database search against a certain resource type. 086 * @param theRequest the request details from the context of the call 087 * @param theResourceType the resource type 088 * @return the partition id which should be used for the database search 089 */ 090 @Nonnull 091 default RequestPartitionId determineReadPartitionForRequestForSearchType( 092 @Nullable RequestDetails theRequest, @Nonnull String theResourceType) { 093 ReadPartitionIdRequestDetails details = 094 ReadPartitionIdRequestDetails.forSearchType(theResourceType, SearchParameterMap.newSynchronous(), null); 095 return determineReadPartitionForRequest(theRequest, details); 096 } 097 098 /** 099 * Determine partition to use when performing a database search based on a resource type and other search parameters. 100 * @param theRequest the request details from the context of the call 101 * @param theResourceType the resource type 102 * @param theParams the search parameters 103 * @return the partition id which should be used for the database search 104 */ 105 @Nonnull 106 default RequestPartitionId determineReadPartitionForRequestForSearchType( 107 @Nullable RequestDetails theRequest, 108 @Nonnull String theResourceType, 109 @Nonnull SearchParameterMap theParams) { 110 ReadPartitionIdRequestDetails details = 111 ReadPartitionIdRequestDetails.forSearchType(theResourceType, theParams, null); 112 return determineReadPartitionForRequest(theRequest, details); 113 } 114 115 /** 116 * Determine partition to use when performing a database search based on a resource type, search parameters and a conditional target resource (if available). 117 * @param theRequest the request details from the context of the call 118 * @param theResourceType the resource type 119 * @param theParams the search parameters 120 * @param theConditionalOperationTargetOrNull the conditional target resource 121 * @return the partition id which should be used for the database search 122 */ 123 @Nonnull 124 default RequestPartitionId determineReadPartitionForRequestForSearchType( 125 RequestDetails theRequest, 126 String theResourceType, 127 SearchParameterMap theParams, 128 IBaseResource theConditionalOperationTargetOrNull) { 129 SearchParameterMap searchParameterMap = theParams != null ? theParams : SearchParameterMap.newSynchronous(); 130 ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forSearchType( 131 theResourceType, searchParameterMap, theConditionalOperationTargetOrNull); 132 return determineReadPartitionForRequest(theRequest, details); 133 } 134 135 RequestPartitionId determineGenericPartitionForRequest(RequestDetails theRequestDetails); 136 137 /** 138 * Determine partition to use when performing the history operation based on a resource type and resource instance. 139 * @param theRequest the request details from the context of the call 140 * @param theResourceType the resource type 141 * @param theIdType the id of the resource instance 142 * @return the partition id which should be used for the history operation 143 */ 144 @Nonnull 145 default RequestPartitionId determineReadPartitionForRequestForHistory( 146 @Nullable RequestDetails theRequest, String theResourceType, IIdType theIdType) { 147 ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forHistory(theResourceType, theIdType); 148 return determineReadPartitionForRequest(theRequest, details); 149 } 150 151 default void validateHasPartitionPermissions( 152 @Nonnull RequestDetails theRequest, String theResourceType, RequestPartitionId theRequestPartitionId) {} 153 154 @Nonnull 155 RequestPartitionId determineCreatePartitionForRequest( 156 @Nullable RequestDetails theRequest, @Nonnull IBaseResource theResource, @Nonnull String theResourceType); 157 158 @Nonnull 159 Set<Integer> toReadPartitions(@Nonnull RequestPartitionId theRequestPartitionId); 160 161 boolean isResourcePartitionable(String theResourceType); 162 163 /** 164 * <b>No interceptors should be invoked by this method. It should ONLY be used when partition ids are 165 * known, but partition names are not.</b> 166 * <br/><br/> 167 * Ensures the list of partition ids inside the given {@link RequestPartitionId} correctly map to the 168 * list of partition names. If the list of partition names is empty, this method will map the correct 169 * partition names and return a normalized {@link RequestPartitionId}. 170 * <br/><br/> 171 * @param theRequestPartitionId - An unvalidated and unnormalized {@link RequestPartitionId}. 172 * @return - A {@link RequestPartitionId} with a normalized list of partition ids and partition names. 173 */ 174 RequestPartitionId validateAndNormalizePartitionIds(RequestPartitionId theRequestPartitionId); 175 176 /** 177 * <b>No interceptors should be invoked by this method. It should ONLY be used when partition names are 178 * known, but partition ids are not.</b> 179 * <br/><br/> 180 * Ensures the list of partition names inside the given {@link RequestPartitionId} correctly map to the 181 * list of partition ids. If the list of partition ids is empty, this method will map the correct 182 * partition ids and return a normalized {@link RequestPartitionId}. 183 * <br/><br/> 184 * @param theRequestPartitionId - An unvalidated and unnormalized {@link RequestPartitionId}. 185 * @return - A {@link RequestPartitionId} with a normalized list of partition ids and partition names. 186 */ 187 RequestPartitionId validateAndNormalizePartitionNames(RequestPartitionId theRequestPartitionId); 188 189 /** 190 * This method returns the default partition ID. Implementers of this interface should overwrite this method to provide 191 * a default partition ID that is different than the default value of null. 192 * 193 * @return the default partition ID 194 */ 195 @Nullable 196 default Integer getDefaultPartitionId() { 197 return null; 198 } 199 200 /** 201 * Test whether <code>theRequestPartitionId</code> is only targeting the default partition where the ID of the default 202 * partition is provided by {@link #getDefaultPartitionId()}. 203 * 204 * @param theRequestPartitionId to perform the evaluation upon. 205 * @return true if the <code>theRequestPartitionId</code> is for the default partition only. 206 */ 207 default boolean isDefaultPartition(@Nonnull RequestPartitionId theRequestPartitionId) { 208 return theRequestPartitionId.isPartition(getDefaultPartitionId()); 209 } 210 211 /** 212 * Test whether <code>theRequestPartitionId</code> has one of its targeted partitions matching the default partition 213 * where the ID of the default partition is provided by {@link #getDefaultPartitionId()}. 214 * 215 * @param theRequestPartitionId to perform the evaluation upon. 216 * @return true if the <code>theRequestPartitionId</code> is targeting the default partition. 217 */ 218 default boolean hasDefaultPartitionId(@Nonnull RequestPartitionId theRequestPartitionId) { 219 return theRequestPartitionId.hasDefaultPartitionId(getDefaultPartitionId()); 220 } 221 222 /** 223 * Given a request partition (which might be "all partitions" or a selection of partitions), 224 * checks if the partition ID in a {@link JpaPid} is within the request partition's range. 225 * 226 * @param theRequestPartitionId The request partition 227 * @param thePid The PID to check for suitability in the request partition 228 */ 229 default boolean isPidPartitionWithinRequestPartition( 230 @Nullable RequestPartitionId theRequestPartitionId, @Nonnull IResourcePersistentId<?> thePid) { 231 if (theRequestPartitionId == null) { 232 return true; 233 } 234 if (theRequestPartitionId.isAllPartitions()) { 235 return true; 236 } 237 if (isDefaultPartition(theRequestPartitionId)) { 238 return Objects.equals(thePid.getPartitionId(), getDefaultPartitionId()); 239 } 240 return !theRequestPartitionId.hasPartitionIds() 241 || theRequestPartitionId.getPartitionIds().contains(thePid.getPartitionId()); 242 } 243}