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