views:

108

answers:

4

Is there a way of intercepting all new Hibernate sessions when they're created? I need to access each Session instance to enable a Hibernate filter with a parameter.

The only solution I've gotten working has involved wrapping the SessionFactory, but this involved a lot of semi nasty hacks as well as it required me to implement around 60 methods, where only a few are interesting.

Hibernate's SessionFactory implementation is for some annoying reason declared final so extending it is not an option. I've also tried aspects and Java proxies without any luck.

+1  A: 

Take a Look at the Hibernate-filter plugin - this may be what you want to use or you can at least see how that plugin does it.

Also I believe that the Multi-tenant plugin may have some code that uses Hibernate Session filters.

Colin Harrington
+1 the hibernate filter plugin is great, we use it to "soft delete" domain objects and it's seamless, check out this post for an example: http://www.intelligrape.com/blog/2010/07/13/grails-hibernate-filter-plugin-a-life-saver/
Ted Naleid
The Hibernate-filter plugin only enables filters per http request, and only for the current session. As far as I can tell any new sessions created during the request will not be configured with the default filters. I'm actually trying to replace the multi-tenant plugin. It (falcone-util) wraps each HibernateSeession and Session in order to rewrite criterias and queries. Using Hibernate filters I hope to avoid wrapping these Hibernate classes and the mess involved with doing so.
Kimble
A: 

It would probably cleanest to have only one place in code where you request a new session from hibernate (for instance in an abstract base class of your DAOs), and enable your filter there.

meriton
That would indeed be the best solution, but Spring / Grails is responsible for this.
Kimble
+1  A: 

I was able to create a JDK proxy:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Date;

import org.hibernate.SessionFactory;
import org.hibernate.engine.SessionFactoryImplementor;

public class SessionFactoryProxyCreator {

   public static SessionFactory instance;

   public static SessionFactory createProxy(final SessionFactory realSessionFactory) {
      ClassLoader cl = SessionFactory.class.getClassLoader();
      Class<?>[] interfaces = new Class[] { SessionFactory.class, SessionFactoryImplementor.class };
      instance = (SessionFactory)Proxy.newProxyInstance(cl, interfaces, new InvocationHandler() {
         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            if ("openSession".equals(method.getName())) {
               System.out.println("NEW SESSION AT " + new Date());
            }

            return method.invoke(realSessionFactory, args);
         }
      });

      return instance;
   }
}

and you would call this from a custom SessionFactoryBean:

import org.codehaus.groovy.grails.orm.hibernate.ConfigurableLocalSessionFactoryBean;
import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class MyConfigurableLocalSessionFactoryBean extends ConfigurableLocalSessionFactoryBean {

   public MyConfigurableLocalSessionFactoryBean() {
      setCurrentSessionContextClass(MyCurrentSessionContext.class);
   }

   @Override
   protected SessionFactory buildSessionFactory() throws Exception {
      setExposeTransactionAwareSessionFactory(false);
      return SessionFactoryProxyCreator.createProxy(super.buildSessionFactory());
   }

   @Override
   protected SessionFactory newSessionFactory(Configuration config) throws HibernateException {
      setExposeTransactionAwareSessionFactory(false);
      return SessionFactoryProxyCreator.createProxy(super.newSessionFactory(config));
   }
}

which depends on a modified version of Spring's SpringSessionContext that uses the proxy instead of the real session factory:

import org.hibernate.HibernateException;
import org.hibernate.classic.Session;
import org.hibernate.context.CurrentSessionContext;
import org.hibernate.engine.SessionFactoryImplementor;
import org.springframework.orm.hibernate3.SessionFactoryUtils;

public class MyCurrentSessionContext implements CurrentSessionContext {

   public MyCurrentSessionContext(SessionFactoryImplementor sessionFactory) {
      // ignore the real sessionFactory, need to use the proxy
   }

   public Session currentSession() throws HibernateException {
      try {
         return (org.hibernate.classic.Session)SessionFactoryUtils.doGetSession(
               SessionFactoryProxyCreator.instance, false);
      }
      catch (IllegalStateException e) {
         throw new HibernateException(e.getMessage());
      }
   }
}

This needs to be registered in resources.groovy to replace the standard Grails ConfigurableLocalSessionFactoryBean:

import org.codehaus.groovy.grails.commons.ApplicationHolder as AH
import org.codehaus.groovy.grails.orm.hibernate.events.PatchedDefaultFlushEventListener

beans = {

   sessionFactory(MyConfigurableLocalSessionFactoryBean) {

      def ds = AH.application.config.dataSource
      def hibConfig = AH.application.config.hibernate

      dataSource = ref('dataSource')
      List hibConfigLocations = []
      if (AH.application.classLoader.getResource('hibernate.cfg.xml')) {
         hibConfigLocations << 'classpath:hibernate.cfg.xml'
      }
      def explicitLocations = hibConfig?.config?.location
      if (explicitLocations) {
         if (explicitLocations instanceof Collection) {
            hibConfigLocations.addAll(explicitLocations.collect { it.toString() })
         }
         else {
            hibConfigLocations << hibConfig.config.location.toString()
         }
      }
      configLocations = hibConfigLocations
      if (ds?.configClass) {
         configClass = ds.configClass
      }
      hibernateProperties = ref('hibernateProperties')
      grailsApplication = ref('grailsApplication', true)
      lobHandler = ref('lobHandlerDetector')
      entityInterceptor = ref('entityInterceptor')
      eventListeners = ['flush': new PatchedDefaultFlushEventListener(),
                        'pre-load':    ref('eventTriggeringInterceptor'),
                        'post-load':   ref('eventTriggeringInterceptor'),
                        'save':        ref('eventTriggeringInterceptor'),
                        'save-update': ref('eventTriggeringInterceptor'),
                        'post-insert': ref('eventTriggeringInterceptor'),
                        'pre-update':  ref('eventTriggeringInterceptor'),
                        'post-update': ref('eventTriggeringInterceptor'),
                        'pre-delete':  ref('eventTriggeringInterceptor'),
                        'post-delete': ref('eventTriggeringInterceptor')]
   }
}
Burt Beckwith
Thanks for the long answer! I was also able to create a proxy for the sessionFactory bean using a post processor and ran into the same "not bound to thread" problem as you did. I never found out what caused it, but since it broke compatibility with other Grails plugins I didn't pursue the issue.
Kimble
I figured it out. SessionFactoryImpl creates a SpringSessionContext instance with itself as a constructor parameter, so while the proxy is the thread-local session factory, it can't be found using the real instance as a map key. So I tweaked the code to use a modified SpringSessionContext that ignores the provided instance and uses the proxy.
Burt Beckwith
A: 

I've solved this problem (at least until Hibernate provides a proper API for things like this). Short version of the solution:

  1. Proxy the session factory
  2. Intercept method invocations to getCurrentSession and use a CurrentSessionContext implementation we've initialized (not Hibernate).

Longer version: http://www.developer-b.com/blog/entry/1635/2010/oct/07/intercepting-hibernate-sessions

Sources / Github: http://github.com/multi-tenant/grails-hibernate-hijacker (still very experimental)

Thanks for the input!

Kimble