
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.rest.server.interceptor.partition; 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.api.Pointcut; 026import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails; 027import ca.uhn.fhir.interceptor.model.RequestPartitionId; 028import ca.uhn.fhir.jpa.model.config.PartitionSettings; 029import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 030import ca.uhn.fhir.rest.api.server.RequestDetails; 031import ca.uhn.fhir.rest.api.server.SystemRequestDetails; 032import ca.uhn.fhir.rest.param.ReferenceParam; 033import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 034import ca.uhn.fhir.rest.server.provider.ProviderConstants; 035import ca.uhn.fhir.rest.server.tenant.ITenantIdentificationStrategy; 036import jakarta.annotation.Nonnull; 037import org.springframework.beans.factory.annotation.Autowired; 038 039import java.util.Collection; 040 041import static org.apache.commons.lang3.StringUtils.isBlank; 042 043/** 044 * This interceptor uses the request tenant ID (as supplied to the server using 045 * {@link ca.uhn.fhir.rest.server.RestfulServer#setTenantIdentificationStrategy(ITenantIdentificationStrategy)}) 046 * to indicate the partition ID. With this interceptor registered, The server treats the tenant name 047 * supplied by the {@link ITenantIdentificationStrategy tenant identification strategy} as a partition name. 048 * <p> 049 * Partition names (aka tenant IDs) must be registered in advance using the partition management operations. 050 * </p> 051 * 052 * @since 5.0.0 053 */ 054@Interceptor 055public class RequestTenantPartitionInterceptor { 056 057 @Autowired 058 private PartitionSettings myPartitionSettings; 059 060 public void setPartitionSettings(PartitionSettings thePartitionSettings) { 061 myPartitionSettings = thePartitionSettings; 062 } 063 064 @Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_READ) 065 public RequestPartitionId partitionIdentifyRead( 066 RequestDetails theRequestDetails, ReadPartitionIdRequestDetails theReadDetails) { 067 /* 068 * If we're configured to allow cross-partition references, and we're performing a search with references, 069 * we'll switch the search to being performed across all partitions. 070 * 071 * A potential future enhancement could be to look at the actual references and figure out which tenants they 072 * refer to. We don't currently allow the tenant to actually be stored in the reference currently, so this 073 * might or might not be a good idea. More thought is needed. 074 */ 075 if (myPartitionSettings.isAllowUnqualifiedCrossPartitionReference()) { 076 if (theRequestDetails.getRestOperationType() == RestOperationTypeEnum.SEARCH_TYPE 077 && theReadDetails.getSearchParams() != null) { 078 boolean haveReferences = theReadDetails.getSearchParams().entrySet().stream() 079 .flatMap(t -> t.getValue().stream()) 080 .flatMap(Collection::stream) 081 .filter(t -> t instanceof ReferenceParam) 082 .map(t -> (ReferenceParam) t) 083 .anyMatch(t -> t.getChain() == null); 084 if (haveReferences) { 085 return RequestPartitionId.allPartitions(); 086 } 087 } 088 } 089 090 return extractPartitionIdFromRequest(theRequestDetails); 091 } 092 093 @Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE) 094 public RequestPartitionId partitionIdentifyCreate(RequestDetails theRequestDetails) { 095 return extractPartitionIdFromRequest(theRequestDetails); 096 } 097 098 @Nonnull 099 protected RequestPartitionId extractPartitionIdFromRequest(RequestDetails theRequestDetails) { 100 101 // We will use the tenant ID that came from the request as the partition name 102 String tenantId = theRequestDetails.getTenantId(); 103 if (isBlank(tenantId)) { 104 // this branch is no-op happen when "partitioning.tenant_identification_strategy" is set to URL_BASED 105 if (theRequestDetails instanceof SystemRequestDetails requestDetails) { 106 if (requestDetails.getRequestPartitionId() != null) { 107 return requestDetails.getRequestPartitionId(); 108 } 109 return RequestPartitionId.defaultPartition(myPartitionSettings); 110 } 111 throw new InternalErrorException(Msg.code(343) + "No partition ID has been specified"); 112 } 113 114 // for REQUEST_TENANT partition selection mode, allPartitions is supported when URL includes _ALL as the tenant 115 // else if no tenant is provided in the URL, DEFAULT will be used as per UrlBaseTenantIdentificationStrategy 116 if (tenantId.equals(ProviderConstants.ALL_PARTITIONS_TENANT_NAME)) { 117 return RequestPartitionId.allPartitions(); 118 } 119 return RequestPartitionId.fromPartitionName(tenantId); 120 } 121}