
001/*- 002 * #%L 003 * HAPI FHIR JPA Server 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.util; 021 022import ca.uhn.fhir.context.ConfigurationException; 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider; 025import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; 026import ca.uhn.fhir.jpa.migrate.JdbcUtils; 027import ca.uhn.fhir.jpa.model.config.PartitionSettings; 028import ca.uhn.fhir.jpa.model.dialect.IHapiFhirDialect; 029import ca.uhn.fhir.system.HapiSystemProperties; 030import org.apache.commons.collections4.SetUtils; 031import org.hibernate.dialect.Dialect; 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034import org.springframework.context.event.ContextRefreshedEvent; 035import org.springframework.context.event.EventListener; 036import org.springframework.transaction.PlatformTransactionManager; 037import org.springframework.transaction.support.TransactionTemplate; 038 039import java.sql.SQLException; 040import java.util.Set; 041import java.util.TreeSet; 042import javax.sql.DataSource; 043 044/** 045 * This bean simply performs a startup check that the database schema is 046 * appropriate for the Partitioned ID mode set in configuration. 047 * If partitioned ID mode is active then the PARTITION_ID column 048 * should be a part of primary keys. 049 */ 050public class PartitionedIdModeVerificationSvc { 051 052 private static final Logger ourLog = LoggerFactory.getLogger(PartitionedIdModeVerificationSvc.class); 053 054 private final PartitionSettings myPartitionSettings; 055 private final HibernatePropertiesProvider myHibernatePropertiesProvider; 056 private final PlatformTransactionManager myTxManager; 057 058 /** 059 * Constructor 060 */ 061 public PartitionedIdModeVerificationSvc( 062 PartitionSettings thePartitionSettings, 063 HibernatePropertiesProvider theHibernatePropertiesProvider, 064 PlatformTransactionManager theTxManager) { 065 myPartitionSettings = thePartitionSettings; 066 myHibernatePropertiesProvider = theHibernatePropertiesProvider; 067 myTxManager = theTxManager; 068 } 069 070 @EventListener(classes = {ContextRefreshedEvent.class}) 071 public void verifyPartitionedIdMode() throws SQLException { 072 073 DataSource dataSource = myHibernatePropertiesProvider.getDataSource(); 074 boolean expectDatabasePartitionMode = myPartitionSettings.isDatabasePartitionMode(); 075 076 Dialect dialect = myHibernatePropertiesProvider.getDialect(); 077 if (!(dialect instanceof IHapiFhirDialect)) { 078 ourLog.warn("Dialect is not a HAPI FHIR dialect: {}", dialect); 079 return; 080 } 081 082 DriverTypeEnum driverType = ((IHapiFhirDialect) dialect).getDriverType(); 083 TransactionTemplate transactionTemplate = new TransactionTemplate(myTxManager); 084 DriverTypeEnum.ConnectionProperties cp = 085 new DriverTypeEnum.ConnectionProperties(dataSource, transactionTemplate, driverType); 086 verifySchemaIsAppropriateForDatabasePartitionMode(cp, expectDatabasePartitionMode); 087 } 088 089 public static void verifySchemaIsAppropriateForDatabasePartitionMode( 090 DriverTypeEnum.ConnectionProperties cp, boolean expectDatabasePartitionMode) throws SQLException { 091 092 if (HapiSystemProperties.isDisableDatabasePartitionModeSchemaCheck()) { 093 return; 094 } 095 096 Set<String> pkColumns = JdbcUtils.getPrimaryKeyColumns(cp, "HFJ_RESOURCE"); 097 if (pkColumns.isEmpty()) { 098 return; 099 } 100 101 if (!expectDatabasePartitionMode) { 102 if (!SetUtils.isEqualSet(pkColumns, Set.of("RES_ID"))) { 103 throw new ConfigurationException(Msg.code(2563) 104 + "System is configured in Partitioned ID mode but the database schema is not correct for this. Found HFJ_RESOURCE PK: " 105 + new TreeSet<>(pkColumns)); 106 } 107 } else { 108 if (!SetUtils.isEqualSet(pkColumns, Set.of("RES_ID", "PARTITION_ID"))) { 109 throw new ConfigurationException(Msg.code(2564) 110 + "System is configured in Partitioned ID mode but the database schema is not correct for this. Found HFJ_RESOURCE PK: " 111 + new TreeSet<>(pkColumns)); 112 } 113 } 114 } 115}