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.config.util; 021 022import ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; 024import ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect; 025import ca.uhn.fhir.jpa.config.HapiFhirLocalContainerEntityManagerFactoryBean; 026import ca.uhn.fhir.jpa.model.config.PartitionSettings; 027import ca.uhn.fhir.jpa.model.util.JpaConstants; 028import ca.uhn.fhir.jpa.util.ISequenceValueMassager; 029import ca.uhn.fhir.util.ReflectionUtil; 030import ca.uhn.hapi.fhir.sql.hibernatesvc.HapiHibernateDialectSettingsService; 031import jakarta.persistence.spi.PersistenceUnitInfo; 032import org.hibernate.boot.registry.BootstrapServiceRegistry; 033import org.hibernate.boot.registry.StandardServiceRegistryBuilder; 034import org.hibernate.jpa.HibernatePersistenceProvider; 035import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl; 036import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor; 037import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder; 038import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 039import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 040 041import java.util.Map; 042import java.util.Objects; 043import java.util.Properties; 044 045public final class HapiEntityManagerFactoryUtil { 046 private HapiEntityManagerFactoryUtil() {} 047 048 /** 049 * This method provides a partially completed entity manager 050 * factory with HAPI FHIR customizations 051 */ 052 public static LocalContainerEntityManagerFactoryBean newEntityManagerFactory( 053 ConfigurableListableBeanFactory myConfigurableListableBeanFactory, 054 FhirContext theFhirContext, 055 JpaStorageSettings theStorageSettings) { 056 057 LocalContainerEntityManagerFactoryBean retVal = 058 new HapiFhirLocalContainerEntityManagerFactoryBean(myConfigurableListableBeanFactory); 059 060 configureEntityManagerFactory(retVal, theFhirContext, theStorageSettings); 061 062 PartitionSettings partitionSettings = myConfigurableListableBeanFactory.getBean(PartitionSettings.class); 063 if (partitionSettings.isDatabasePartitionMode()) { 064 Properties properties = new Properties(); 065 properties.put(JpaConstants.HAPI_DATABASE_PARTITION_MODE, Boolean.toString(true)); 066 // Despite the fast that the name make it sound purely like a setter, the method 067 // below just merges this property in, so it won't get overwritten later or 068 // overwrite other properties 069 retVal.setJpaProperties(properties); 070 } 071 072 return retVal; 073 } 074 075 public static void configureEntityManagerFactory( 076 LocalContainerEntityManagerFactoryBean theFactory, 077 FhirContext theFhirContext, 078 JpaStorageSettings theStorageSettings) { 079 theFactory.setJpaDialect(new HapiFhirHibernateJpaDialect(theFhirContext.getLocalizer())); 080 theFactory.setPackagesToScan("ca.uhn.fhir.jpa.model.entity", "ca.uhn.fhir.jpa.entity"); 081 theFactory.setPersistenceProvider(new MyHibernatePersistenceProvider(theStorageSettings)); 082 } 083 084 private static class MyHibernatePersistenceProvider extends HibernatePersistenceProvider { 085 086 private final JpaStorageSettings myStorageSettings; 087 private boolean myDatabasePartitionMode; 088 089 public MyHibernatePersistenceProvider(JpaStorageSettings theStorageSettings) { 090 myStorageSettings = theStorageSettings; 091 } 092 093 /** 094 * @see MyEntityManagerFactoryBuilderImpl for an explanation of why we do this 095 */ 096 @Override 097 protected EntityManagerFactoryBuilder getEntityManagerFactoryBuilder( 098 PersistenceUnitInfo info, Map<?, ?> theIntegration) { 099 100 String databasePartitionModeString = (String) theIntegration.get(JpaConstants.HAPI_DATABASE_PARTITION_MODE); 101 databasePartitionModeString = 102 Objects.toString(databasePartitionModeString, JpaConstants.HAPI_DATABASE_PARTITION_MODE_DEFAULT); 103 myDatabasePartitionMode = Boolean.parseBoolean(databasePartitionModeString); 104 105 return new MyEntityManagerFactoryBuilderImpl(info, theIntegration); 106 } 107 108 protected boolean isDatabasePartitionMode() { 109 return myDatabasePartitionMode; 110 } 111 112 /** 113 * This class extends the default hibernate EntityManagerFactoryBuilder in order to 114 * register a custom service (the {@link ISequenceValueMassager}), which is used in 115 * {@link ca.uhn.fhir.jpa.model.dialect.HapiSequenceStyleGenerator}. 116 * <p> 117 * In Hibernate 5 we didn't need to do this, since we could just register 118 * the service with Spring and Hibernate would ask Spring for it. This no longer 119 * seems to work in Hibernate 6, so we now have to manually register it. 120 */ 121 private class MyEntityManagerFactoryBuilderImpl extends EntityManagerFactoryBuilderImpl { 122 123 @SuppressWarnings({"unchecked"}) 124 private MyEntityManagerFactoryBuilderImpl(PersistenceUnitInfo theInfo, Map<?, ?> theIntegration) { 125 super(new PersistenceUnitInfoDescriptor(theInfo), (Map<String, Object>) theIntegration); 126 } 127 128 @Override 129 protected StandardServiceRegistryBuilder getStandardServiceRegistryBuilder( 130 BootstrapServiceRegistry theBootstrapServiceRegistry) { 131 HapiHibernateDialectSettingsService service = new HapiHibernateDialectSettingsService(); 132 service.setDatabasePartitionMode(isDatabasePartitionMode()); 133 134 StandardServiceRegistryBuilder retVal = 135 super.getStandardServiceRegistryBuilder(theBootstrapServiceRegistry); 136 ISequenceValueMassager sequenceValueMassager = 137 ReflectionUtil.newInstance(myStorageSettings.getSequenceValueMassagerClass()); 138 retVal.addService(ISequenceValueMassager.class, sequenceValueMassager); 139 retVal.addService(HapiHibernateDialectSettingsService.class, service); 140 return retVal; 141 } 142 } 143 } 144}