001/*-
002 * #%L
003 * HAPI FHIR Storage api
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.interceptor;
021
022import ca.uhn.fhir.i18n.Msg;
023import ca.uhn.fhir.interceptor.api.Hook;
024import ca.uhn.fhir.interceptor.api.Interceptor;
025import ca.uhn.fhir.interceptor.model.IDefaultPartitionSettings;
026import ca.uhn.fhir.interceptor.model.RequestPartitionId;
027import ca.uhn.fhir.rest.api.Constants;
028import ca.uhn.fhir.rest.api.server.RequestDetails;
029import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
030import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
031import ca.uhn.fhir.rest.server.messaging.RequestPartitionHeaderUtil;
032
033import java.util.function.BiFunction;
034
035import static ca.uhn.fhir.interceptor.api.Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE;
036import static ca.uhn.fhir.interceptor.api.Pointcut.STORAGE_PARTITION_IDENTIFY_READ;
037import static org.apache.commons.lang3.StringUtils.isBlank;
038
039/**
040 * This is an interceptor to identify the partition ID from a request header.
041 * It reads the value of the X-Request-Partition-IDs header, which is expected to be a comma separated partition ids.
042 * For the read operations it uses all the partitions specified in the header.
043 * The create operations it uses the first partition ID from the header.
044 * <p>
045 * The tests for the functionality of this interceptor can be found in the
046 * ca.uhn.fhir.jpa.interceptor.RequestHeaderPartitionTest class.
047 */
048@Interceptor
049public class RequestHeaderPartitionInterceptor {
050        private final IDefaultPartitionSettings myDefaultPartitionSettings;
051
052        public RequestHeaderPartitionInterceptor(IDefaultPartitionSettings theDefaultPartitionSettings) {
053                myDefaultPartitionSettings = theDefaultPartitionSettings;
054        }
055
056        /**
057         * Identifies the partition ID for create operations by parsing the first ID from the X-Request-Partition-IDs header.
058         */
059        @Hook(STORAGE_PARTITION_IDENTIFY_CREATE)
060        public RequestPartitionId identifyPartitionForCreate(RequestDetails theRequestDetails) {
061                return identifyPartitionOrThrowException(
062                                theRequestDetails, RequestPartitionHeaderUtil::fromHeaderFirstPartitionOnly);
063        }
064
065        /**
066         * Identifies partition IDs for read operations by parsing all IDs from the X-Request-Partition-IDs header.
067         */
068        @Hook(STORAGE_PARTITION_IDENTIFY_READ)
069        public RequestPartitionId identifyPartitionForRead(RequestDetails theRequestDetails) {
070                return identifyPartitionOrThrowException(theRequestDetails, RequestPartitionHeaderUtil::fromHeader);
071        }
072
073        /**
074         * Core logic to identify a request's storage partition. It retrieves the partition header,
075         * and if the header is blank for a system request, it uses the partition id in the system request if present or
076         * returns the default partition.
077         * Otherwise, it uses the provided parsing function to interpret the header.
078         */
079        private RequestPartitionId identifyPartitionOrThrowException(
080                        RequestDetails theRequestDetails,
081                        BiFunction<String, IDefaultPartitionSettings, RequestPartitionId> aHeaderParser) {
082                String partitionHeader = theRequestDetails.getHeader(Constants.HEADER_X_REQUEST_PARTITION_IDS);
083
084                if (isBlank(partitionHeader)) {
085                        if (theRequestDetails instanceof SystemRequestDetails systemRequestDetails) {
086                                if (systemRequestDetails.getRequestPartitionId() != null) {
087                                        return systemRequestDetails.getRequestPartitionId();
088                                } else {
089                                        return myDefaultPartitionSettings.getDefaultRequestPartitionId();
090                                }
091                        }
092                        throw new InvalidRequestException(Msg.code(2642)
093                                        + String.format(
094                                                        "%s header is missing or blank, it is required to identify the storage partition",
095                                                        Constants.HEADER_X_REQUEST_PARTITION_IDS));
096                }
097
098                return aHeaderParser.apply(partitionHeader, myDefaultPartitionSettings);
099        }
100}