diff --git a/src/java/org/codehaus/groovy/grails/orm/hibernate/ConfigurableLocalSessionFactoryBean.java b/src/java/org/codehaus/groovy/grails/orm/hibernate/ConfigurableLocalSessionFactoryBean.java index ca3e2f4abdc..3972d05e445 100644 --- a/src/java/org/codehaus/groovy/grails/orm/hibernate/ConfigurableLocalSessionFactoryBean.java +++ b/src/java/org/codehaus/groovy/grails/orm/hibernate/ConfigurableLocalSessionFactoryBean.java @@ -17,6 +17,7 @@ import groovy.lang.GroovySystem; import groovy.lang.MetaClassRegistry; +import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.util.Map; @@ -25,12 +26,14 @@ import org.codehaus.groovy.grails.commons.GrailsApplication; import org.codehaus.groovy.grails.orm.hibernate.cfg.DefaultGrailsDomainConfiguration; import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsDomainConfiguration; +import org.codehaus.groovy.grails.orm.hibernate.events.SaveOrUpdateEventListener; import org.hibernate.EntityMode; import org.hibernate.HibernateException; import org.hibernate.SessionFactory; import org.hibernate.cache.CacheException; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; +import org.hibernate.event.*; import org.hibernate.metadata.ClassMetadata; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; @@ -53,6 +56,7 @@ public class ConfigurableLocalSessionFactoryBean extends private GrailsApplication grailsApplication; private Class configClass; private Class currentSessionContextClass; + private HibernateEventListeners hibernateEventListeners; /** * Sets class to be used for the Hibernate CurrentSessionContext @@ -149,10 +153,6 @@ private boolean isCacheConfigurationError(Throwable cause) { return cause != null && (cause instanceof CacheException); } - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - // not used - } - @Override public void destroy() throws HibernateException { MetaClassRegistry registry = GroovySystem.getMetaClassRegistry(); @@ -164,5 +164,112 @@ public void destroy() throws HibernateException { } super.destroy(); } -} + /** + * Merge HibernateEventListeners with the default ones + */ + @Override + protected void postProcessConfiguration(final Configuration config) throws HibernateException { + super.postProcessConfiguration(config); + if (hibernateEventListeners == null || hibernateEventListeners.getListenerMap() == null) { + return; + } + + EventListeners listeners = config.getEventListeners(); + Map listenerMap = hibernateEventListeners.getListenerMap(); + addNewListenerToConfiguration(config, "auto-flush", AutoFlushEventListener.class, + listeners.getAutoFlushEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "merge", MergeEventListener.class, + listeners.getMergeEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "create", PersistEventListener.class, + listeners.getPersistEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "create-onflush", PersistEventListener.class, + listeners.getPersistOnFlushEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "delete", DeleteEventListener.class, + listeners.getDeleteEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "dirty-check", DirtyCheckEventListener.class, + listeners.getDirtyCheckEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "evict", EvictEventListener.class, + listeners.getEvictEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "flush", FlushEventListener.class, + listeners.getFlushEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "flush-entity", FlushEntityEventListener.class, + listeners.getFlushEntityEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "load", LoadEventListener.class, + listeners.getLoadEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "load-collection", InitializeCollectionEventListener.class, + listeners.getInitializeCollectionEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "lock", LockEventListener.class, + listeners.getLockEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "refresh", RefreshEventListener.class, + listeners.getRefreshEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "replicate", ReplicateEventListener.class, + listeners.getReplicateEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "save-update", SaveOrUpdateEventListener.class, + listeners.getSaveOrUpdateEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "save", SaveOrUpdateEventListener.class, + listeners.getSaveEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "update", SaveOrUpdateEventListener.class, + listeners.getUpdateEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "pre-load", PreLoadEventListener.class, + listeners.getPreLoadEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "pre-update", PreUpdateEventListener.class, + listeners.getPreUpdateEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "pre-delete", PreDeleteEventListener.class, + listeners.getPreDeleteEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "pre-insert", PreInsertEventListener.class, + listeners.getPreInsertEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "pre-collection-recreate", PreCollectionRecreateEventListener.class, + listeners.getPreCollectionRecreateEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "pre-collection-remove", PreCollectionRemoveEventListener.class, + listeners.getPreCollectionRemoveEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "pre-collection-update", PreCollectionUpdateEventListener.class, + listeners.getPreCollectionUpdateEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "post-load", PostLoadEventListener.class, + listeners.getPostLoadEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "post-update", PostUpdateEventListener.class, + listeners.getPostUpdateEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "post-delete", PostDeleteEventListener.class, + listeners.getPostDeleteEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "post-insert", PostInsertEventListener.class, + listeners.getPostInsertEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "post-commit-update", PostUpdateEventListener.class, + listeners.getPostCommitUpdateEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "post-commit-delete", PostDeleteEventListener.class, + listeners.getPostCommitDeleteEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "post-commit-insert", PostInsertEventListener.class, + listeners.getPostCommitInsertEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "post-collection-recreate", PostCollectionRecreateEventListener.class, + listeners.getPostCollectionRecreateEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "post-collection-remove", PostCollectionRemoveEventListener.class, + listeners.getPostCollectionRemoveEventListeners(), listenerMap); + addNewListenerToConfiguration(config, "post-collection-update", PostCollectionUpdateEventListener.class, + listeners.getPostCollectionUpdateEventListeners(), listenerMap); + } + + @SuppressWarnings("unchecked") + private void addNewListenerToConfiguration(final Configuration config, final String listenerType, + final Class klass, final T[] currentListeners, final Map newlistenerMap) { + + Object newListener = newlistenerMap.get(listenerType); + if (newListener == null) return; + + if (currentListeners != null && currentListeners.length > 0) { + T[] newListeners = (T[])Array.newInstance(klass, currentListeners.length + 1); + System.arraycopy(currentListeners, 0, newListeners, 0, currentListeners.length); + newListeners[currentListeners.length] = (T)newListener; + config.setListeners(listenerType, newListeners); + } + else { + config.setListener(listenerType, newListener); + } + } + + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + // not used + } + + public void setHibernateEventListeners(final HibernateEventListeners listeners) { + hibernateEventListeners = listeners; + } +} diff --git a/src/java/org/codehaus/groovy/grails/orm/hibernate/HibernateEventListeners.java b/src/java/org/codehaus/groovy/grails/orm/hibernate/HibernateEventListeners.java new file mode 100644 index 00000000000..f9f09390252 --- /dev/null +++ b/src/java/org/codehaus/groovy/grails/orm/hibernate/HibernateEventListeners.java @@ -0,0 +1,30 @@ +/* Copyright 2004-2010 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 + * + * http://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 org.codehaus.groovy.grails.orm.hibernate; + +import java.util.Map; + +public class HibernateEventListeners { + + private Map listenerMap; + + public Map getListenerMap() { + return listenerMap; + } + + public void setListenerMap(Map listenerMap) { + this.listenerMap = listenerMap; + } +} diff --git a/src/java/org/codehaus/groovy/grails/plugins/orm/hibernate/HibernatePluginSupport.groovy b/src/java/org/codehaus/groovy/grails/plugins/orm/hibernate/HibernatePluginSupport.groovy index 65015d05645..45438d98aa4 100644 --- a/src/java/org/codehaus/groovy/grails/plugins/orm/hibernate/HibernatePluginSupport.groovy +++ b/src/java/org/codehaus/groovy/grails/plugins/orm/hibernate/HibernatePluginSupport.groovy @@ -36,6 +36,7 @@ import org.codehaus.groovy.grails.commons.spring.RuntimeSpringConfiguration import org.codehaus.groovy.grails.exceptions.GrailsDomainException import org.codehaus.groovy.grails.orm.hibernate.ConfigurableLocalSessionFactoryBean import org.codehaus.groovy.grails.orm.hibernate.GrailsHibernateTransactionManager +import org.codehaus.groovy.grails.orm.hibernate.HibernateEventListeners import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsDomainBinder import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsHibernateUtil import org.codehaus.groovy.grails.orm.hibernate.cfg.HibernateNamedQueriesBuilder @@ -185,6 +186,7 @@ Using Grails' default naming strategy: '${GrailsDomainBinder.namingStrategy.getC } proxyHandler(HibernateProxyHandler) eventTriggeringInterceptor(ClosureEventTriggeringInterceptor) + hibernateEventListeners(HibernateEventListeners) entityInterceptor(EmptyInterceptor) sessionFactory(ConfigurableLocalSessionFactoryBean) { dataSource = dataSource @@ -219,6 +221,7 @@ Using Grails' default naming strategy: '${GrailsDomainBinder.namingStrategy.getC 'post-update':eventTriggeringInterceptor, 'pre-delete':eventTriggeringInterceptor, 'post-delete':eventTriggeringInterceptor] + hibernateEventListeners = hibernateEventListeners } transactionManager(GrailsHibernateTransactionManager) { diff --git a/src/test/org/codehaus/groovy/grails/orm/hibernate/AbstractGrailsHibernateTests.groovy b/src/test/org/codehaus/groovy/grails/orm/hibernate/AbstractGrailsHibernateTests.groovy index a6e16e310ec..07c654612f0 100644 --- a/src/test/org/codehaus/groovy/grails/orm/hibernate/AbstractGrailsHibernateTests.groovy +++ b/src/test/org/codehaus/groovy/grails/orm/hibernate/AbstractGrailsHibernateTests.groovy @@ -111,8 +111,7 @@ hibernate { ctx.registerMockBean("messageSource", new StaticMessageSource()) def springConfig = new WebRuntimeSpringConfiguration(ctx, gcl) - dependentPlugins*.doWithRuntimeConfiguration(springConfig) - dependentPlugins.each { mockManager.registerMockPlugin(it); it.manager = mockManager } + doWithRuntimeConfiguration dependentPlugins, springConfig appCtx = springConfig.getApplicationContext() applicationContext = appCtx @@ -129,6 +128,11 @@ hibernate { } } + protected void doWithRuntimeConfiguration(dependentPlugins, springConfig) { + dependentPlugins*.doWithRuntimeConfiguration(springConfig) + dependentPlugins.each { mockManager.registerMockPlugin(it); it.manager = mockManager } + } + protected void tearDown() { if (TransactionSynchronizationManager.hasResource(sessionFactory)) { diff --git a/src/test/org/codehaus/groovy/grails/orm/hibernate/HibernateEventListenerTests.groovy b/src/test/org/codehaus/groovy/grails/orm/hibernate/HibernateEventListenerTests.groovy new file mode 100644 index 00000000000..b3b6ed2f604 --- /dev/null +++ b/src/test/org/codehaus/groovy/grails/orm/hibernate/HibernateEventListenerTests.groovy @@ -0,0 +1,51 @@ +package org.codehaus.groovy.grails.orm.hibernate + +import org.hibernate.event.PostDeleteEvent +import org.hibernate.event.PostDeleteEventListener +import org.hibernate.event.PostInsertEvent +import org.hibernate.event.PostInsertEventListener + +import org.codehaus.groovy.grails.commons.test.AbstractGrailsMockTests +import org.codehaus.groovy.grails.plugins.DefaultGrailsPlugin + +/** + * @author Burt Beckwith + */ +class HibernateEventListenerTests extends AbstractGrailsHibernateTests { + + private plugin + + protected void afterPluginInitialization() { + plugin = new DefaultGrailsPlugin(EventListenerGrailsPlugin, ga) + mockManager.registerMockPlugin plugin + plugin.manager = mockManager + } + + protected void doWithRuntimeConfiguration(dependentPlugins, springConfig) { + super.doWithRuntimeConfiguration dependentPlugins, springConfig + plugin.doWithRuntimeConfiguration springConfig + } + + void testDoRuntimeConfiguration() { + def eventListeners = appCtx.sessionFactory.eventListeners + assertTrue eventListeners.postInsertEventListeners.any { it instanceof TestAuditListener } + assertTrue eventListeners.postDeleteEventListeners.any { it instanceof TestAuditListener } + assertFalse eventListeners.postUpdateEventListeners.any { it instanceof TestAuditListener } + } +} + +class EventListenerGrailsPlugin { + def version = 1 + def doWithSpring = { + testAuditListener(TestAuditListener) + hibernateEventListeners(HibernateEventListeners) { + listenerMap = ['post-insert': testAuditListener, + 'post-delete': testAuditListener] + } + } +} + +class TestAuditListener implements PostInsertEventListener, PostDeleteEventListener { + void onPostInsert(PostInsertEvent event) {} + void onPostDelete(PostDeleteEvent event) {} +}