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}