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}