diff --git a/grace-plugin/build.gradle b/grace-plugin/build.gradle index c2c268f0..d06bcc84 100644 --- a/grace-plugin/build.gradle +++ b/grace-plugin/build.gradle @@ -4,6 +4,7 @@ dependencies { documentation ("org.graceframework:grace-spring:$graceVersion") documentation ("com.github.javaparser:javaparser-core:$javaParserCoreVersion") + implementation("org.springframework.boot:spring-boot-autoconfigure:$springBootVersion") compileOnly("org.graceframework:grace-boot:$graceVersion") compileOnly("org.graceframework:grace-bootstrap:$graceVersion") compileOnly "org.graceframework:grace-core:$graceVersion", { diff --git a/grace-plugin/src/main/groovy/grails/plugin/hibernate/HibernateGormAutoConfiguration.java b/grace-plugin/src/main/groovy/grails/plugin/hibernate/HibernateGormAutoConfiguration.java new file mode 100644 index 00000000..b691a1a5 --- /dev/null +++ b/grace-plugin/src/main/groovy/grails/plugin/hibernate/HibernateGormAutoConfiguration.java @@ -0,0 +1,176 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package grails.plugin.hibernate; + +import java.beans.Introspector; +import java.util.HashSet; +import java.util.Set; + +import javax.sql.DataSource; + +import org.hibernate.SessionFactory; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureOrder; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; +import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.core.annotation.Order; + +import grails.config.Config; +import grails.core.GrailsApplication; +import grails.core.GrailsClass; +import org.grails.core.artefact.DomainClassArtefactHandler; +import org.grails.datastore.gorm.events.ConfigurableApplicationContextEventPublisher; +import org.grails.datastore.gorm.proxy.ProxyHandlerAdapter; +import org.grails.datastore.mapping.model.MappingContext; +import org.grails.datastore.mapping.services.Service; +import org.grails.orm.hibernate.HibernateDatastore; +import org.grails.orm.hibernate.proxy.HibernateProxyHandler; +import org.grails.plugin.hibernate.support.AggregatePersistenceContextInterceptor; +import org.grails.plugin.hibernate.support.GrailsOpenSessionInViewInterceptor; +import org.grails.transaction.ChainedTransactionManagerPostProcessor; + +/** + * {@link EnableAutoConfiguration Auto-Configure} for GORM for Hibernate + * + * @author Michael Yan + * @since 2023.1 + */ +@AutoConfiguration(after = DataSourceAutoConfiguration.class, + before = { HibernateJpaAutoConfiguration.class, TransactionAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class}) +@AutoConfigureOrder +@ConditionalOnClass(HibernateDatastore.class) +public class HibernateGormAutoConfiguration { + + private static final String TRANSACTION_MANAGER_WHITE_LIST_PATTERN = "grails.transaction.chainedTransactionManager.whitelistPattern"; + private static final String TRANSACTION_MANAGER_BLACK_LIST_PATTERN = "grails.transaction.chainedTransactionManager.blacklistPattern"; + + private final ConfigurableApplicationContext applicationContext; + private final ObjectProvider grailsApplication; + + public HibernateGormAutoConfiguration(ApplicationContext applicationContext, + ObjectProvider grailsApplication) { + this.applicationContext = (ConfigurableApplicationContext) applicationContext; + this.grailsApplication = grailsApplication; + } + + @Bean + @ConditionalOnMissingBean + public HibernateDatastore hibernateDatastore(ObjectProvider dataSource) { + GrailsClass[] grailsClasses = this.grailsApplication.getObject().getArtefacts(DomainClassArtefactHandler.TYPE); + Set> domainClasses = new HashSet<>(); + for (GrailsClass grailsClass : grailsClasses) { + if (grailsClass.getClazz() != null) { + domainClasses.add(grailsClass.getClazz()); + } + } + + HibernateDatastore datastore; + if (dataSource.getIfAvailable() != null) { + datastore = new HibernateDatastore( + dataSource.getObject(), + this.applicationContext.getEnvironment(), + new ConfigurableApplicationContextEventPublisher(this.applicationContext), + domainClasses.toArray(new Class[0]) + ); + } + else { + datastore = new HibernateDatastore( + this.applicationContext.getEnvironment(), + new ConfigurableApplicationContextEventPublisher(this.applicationContext), + domainClasses.toArray(new Class[0]) + ); + } + + for (Service service : datastore.getServices()) { + Class serviceClass = service.getClass(); + grails.gorm.services.Service ann = serviceClass.getAnnotation(grails.gorm.services.Service.class); + String serviceName; + if (ann == null) { + serviceName = Introspector.decapitalize(serviceClass.getSimpleName()); + } + else { + serviceName = ann.name(); + } + if (!this.applicationContext.containsBean(serviceName)) { + this.applicationContext.getBeanFactory().registerSingleton( + serviceName, + service + ); + } + } + return datastore; + } + + @Bean + @ConditionalOnMissingBean + public SessionFactory sessionFactory(HibernateDatastore hibernateDatastore) { + return hibernateDatastore.getSessionFactory(); + } + + @Bean + @ConditionalOnMissingBean + public MappingContext grailsDomainClassMappingContext(HibernateDatastore hibernateDatastore) { + return hibernateDatastore.getMappingContext(); + } + + @Bean + @ConditionalOnMissingBean + public AggregatePersistenceContextInterceptor persistenceInterceptor(HibernateDatastore hibernateDatastore) { + return new AggregatePersistenceContextInterceptor(hibernateDatastore); + } + + @Bean + @ConditionalOnMissingBean + public HibernateProxyHandler hibernateProxyHandler() { + return new HibernateProxyHandler(); + } + + @Bean + @Order(10) + @ConditionalOnMissingBean + public ProxyHandlerAdapter proxyHandler(HibernateProxyHandler hibernateProxyHandler) { + return new ProxyHandlerAdapter(hibernateProxyHandler); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(name = "hibernate.osiv.enabled", havingValue = "true", matchIfMissing = true) + public GrailsOpenSessionInViewInterceptor openSessionInViewInterceptor(HibernateDatastore hibernateDatastore) { + GrailsOpenSessionInViewInterceptor openSessionInViewInterceptor = new GrailsOpenSessionInViewInterceptor(); + openSessionInViewInterceptor.setHibernateDatastore(hibernateDatastore); + return openSessionInViewInterceptor; + } + + @Bean + @ConditionalOnProperty(prefix = "grails.transaction.chainedTransactionManager", name = "enabled", havingValue = "true") + public ChainedTransactionManagerPostProcessor chainedTransactionManagerPostProcessor() { + Config config = this.grailsApplication.getObject().getConfig(); + String whitelistPattern = config.getProperty(TRANSACTION_MANAGER_WHITE_LIST_PATTERN, ""); + String blacklistPattern = config.getProperty(TRANSACTION_MANAGER_BLACK_LIST_PATTERN, ""); + return new ChainedTransactionManagerPostProcessor(config, whitelistPattern, blacklistPattern); + } + +} diff --git a/grace-plugin/src/main/groovy/grails/plugin/hibernate/HibernateGrailsPlugin.groovy b/grace-plugin/src/main/groovy/grails/plugin/hibernate/HibernateGrailsPlugin.groovy index 3c53cfbb..5f8d4f95 100644 --- a/grace-plugin/src/main/groovy/grails/plugin/hibernate/HibernateGrailsPlugin.groovy +++ b/grace-plugin/src/main/groovy/grails/plugin/hibernate/HibernateGrailsPlugin.groovy @@ -1,21 +1,30 @@ +/* + * Copyright 2016-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package grails.plugin.hibernate import groovy.transform.CompileStatic -import org.springframework.beans.factory.support.BeanDefinitionRegistry import org.springframework.context.ConfigurableApplicationContext import org.springframework.core.convert.converter.Converter import org.springframework.core.convert.support.ConfigurableConversionService -import org.springframework.core.env.PropertyResolver import grails.config.Config import grails.core.GrailsApplication -import grails.core.GrailsClass -import grails.orm.bootstrap.HibernateDatastoreSpringInitializer import grails.plugins.Plugin -import grails.util.Environment import org.grails.config.PropertySourcesConfig -import org.grails.core.artefact.DomainClassArtefactHandler /** * Plugin that integrates Hibernate into a Grails application @@ -27,8 +36,6 @@ import org.grails.core.artefact.DomainClassArtefactHandler @CompileStatic class HibernateGrailsPlugin extends Plugin { - public static final String DEFAULT_DATA_SOURCE_NAME = HibernateDatastoreSpringInitializer.DEFAULT_DATA_SOURCE_NAME - def grailsVersion = '2023.0.0 > *' def author = 'Grace Framework' @@ -46,8 +53,6 @@ class HibernateGrailsPlugin extends Plugin { def issueManagement = [system: 'Github', url: 'https://github.com/graceframework/grace-data-hibernate/issues'] def scm = [url: 'https://github.com/graceframework/grace-data-hibernate'] - Set dataSourceNames - Closure doWithSpring() { { -> ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) applicationContext @@ -65,26 +70,7 @@ class HibernateGrailsPlugin extends Plugin { }) ((PropertySourcesConfig) config).setConversionService(conversionService) } - - - def domainClasses = grailsApplication.getArtefacts(DomainClassArtefactHandler.TYPE) - .collect() { GrailsClass cls -> cls.clazz } - - def springInitializer = new HibernateDatastoreSpringInitializer((PropertyResolver) config, domainClasses) - springInitializer.enableReload = Environment.isDevelopmentMode() - springInitializer.registerApplicationIfNotPresent = false - springInitializer.grailsPlugin = true - dataSourceNames = springInitializer.dataSources - def beans = springInitializer.getBeanDefinitions((BeanDefinitionRegistry) applicationContext) - - beans.delegate = delegate - beans.call() } } - @Override - void onChange(Map event) { - // TODO: rewrite onChange handling - } - } diff --git a/grace-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/grace-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..309e0e81 --- /dev/null +++ b/grace-plugin/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +grails.plugin.hibernate.HibernateGormAutoConfiguration \ No newline at end of file