package org.jboss.seam.ioc.spring;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.jboss.seam.Component;
import org.jboss.seam.log.LogProvider;
import org.jboss.seam.log.Logging;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.util.ClassUtils;
/**
* A SessionFactory that delegates requests to open a Session to a
* "managed-hibernate-session".
*
* @author Mike Youngstrom
*/
public class SeamManagedSessionFactoryBean extends AbstractFactoryBean
{
private String sessionName;
private SessionFactory baseSessionFactory;
@Override
public void afterPropertiesSet() throws Exception
{
super.afterPropertiesSet();
if (sessionName == null || "".equals(sessionName))
{
throw new IllegalArgumentException("SesssionName cannot be empty");
}
}
@Override
protected Object createInstance() throws Exception
{
Class[] sessionFactoryInterfaces;
if (baseSessionFactory != null)
{
sessionFactoryInterfaces = ClassUtils.getAllInterfaces(baseSessionFactory);
}
else
{
sessionFactoryInterfaces = new Class[] { SessionFactory.class };
}
// Create proxy of SessionFactory to implement all interfaces the
// baseSessionFactory did.
return Proxy.newProxyInstance(getClass().getClassLoader(), sessionFactoryInterfaces,
new SeamManagedSessionFactoryHandler(sessionName, baseSessionFactory));
}
@Override
public Class getObjectType()
{
if (baseSessionFactory != null)
{
return baseSessionFactory.getClass();
}
return SessionFactory.class;
}
/**
* Optionally provide an instance of the SessionFactory we are wrapping. Only
* necessary if the proxy needs to expose access to any interfaces besides
* SessionFactory.class.
*
* @param baseSessionFactory
*/
public void setBaseSessionFactory(SessionFactory baseSessionFactory)
{
this.baseSessionFactory = baseSessionFactory;
}
/**
* The name of the Seam "managed-hibernate-session" component.
*
* @param sessionName
*/
@Required
public void setSessionName(String sessionName)
{
this.sessionName = sessionName;
}
/**
* Proxy for a SessionFactory. Returning a close suppressing proxy on calls
* to "openSession".
*
* @author Mike Youngstrom
*
*/
public static class SeamManagedSessionFactoryHandler implements InvocationHandler, Serializable
{
private SessionFactory rawSessionFactory;
private String sessionName;
private boolean isClosed = false;
public SeamManagedSessionFactoryHandler(String sessionName, SessionFactory rawSessionFactory)
{
this.rawSessionFactory = rawSessionFactory;
this.sessionName = sessionName;
}
private synchronized SessionFactory getRawSessionFactory()
{
if (rawSessionFactory == null)
{
rawSessionFactory = getSession().getSessionFactory();
}
return rawSessionFactory;
}
private Session getSession()
{
SeamLifecycleUtils.beginTransactionalSeamCall();
return (Session) Component.getInstance(sessionName);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
if (method.getName().equals("equals"))
{
// Only consider equal when proxies are identical.
return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
}
if (method.getName().equals("hashCode"))
{
// Use hashCode of SessionFactory proxy.
return new Integer(hashCode());
}
SessionFactory delegate = getRawSessionFactory();
if (method.getName().equals("isClosed"))
{
return delegate.isClosed() || isClosed;
}
if (delegate.isClosed())
{
// Defer to delegate error if it's closed.
try
{
return method.invoke(delegate, args);
}
catch (InvocationTargetException ex)
{
throw ex.getTargetException();
}
}
if (isClosed)
{
throw new IllegalStateException("This SessionFactory is closed.");
}
if (method.getName().equals("close"))
{
Session session = getSession();
session.disconnect();
isClosed = true;
return null;
}
if (method.getName().equals("getCurrentSession"))
{
try
{
return getSession();
}
catch (IllegalStateException ex)
{
throw new HibernateException(ex.getMessage());
}
}
if (method.getName().equals("openSession"))
{
if (method.getParameterTypes().length == 0)
{
Session session = getSession();
// Return close suppressing Session Proxy that implements all
// interfaces the original did
ClassUtils.getAllInterfaces(session);
List<Class> interfaces = new ArrayList<Class>(Arrays.asList(ClassUtils
.getAllInterfaces(session)));
//Have to bend Session implementation since HiberanteSessionProxy doesn't implement classic.Session.
interfaces.add(Session.class);
return Proxy.newProxyInstance(this.getClass().getClassLoader(), interfaces
.toArray(new Class[interfaces.size()]), new SeamManagedSessionHandler(
(SessionFactory) proxy, session));
}
else
{
throw new HibernateException(
"This SeamManagedSessionFactory will only return a session from a call to noarg openSession()");
}
}
if (method.getName().equals("openStatelessSession"))
{
throw new HibernateException("This SessionFactory does not support StatelessSessions");
}
if (method.getName().equals("getReference"))
{
throw new HibernateException(
"A SeamManagedSessionFactory is not referencable. If this is a requirement file a feature request.");
}
try
{
if(method.getDeclaringClass().equals(Session.class) &&
!(delegate instanceof Session)) {
throw new UnsupportedOperationException("Unable to execute method: "+method.toString()+" Seam managed session does not support classic.Session methods.");
}
return method.invoke(delegate, args);
}
catch (InvocationTargetException ex)
{
throw ex.getTargetException();
}
}
}
/**
* Delegates calls to a hibernate session and suppresses calls to close.
*
* @author Mike Youngstrom
*/
public static class SeamManagedSessionHandler implements InvocationHandler, Serializable
{
private static final LogProvider log = Logging
.getLogProvider(SeamManagedSessionHandler.class);
private Session delegate;
private SessionFactory sessionFactory;
private boolean closed = false;
public SeamManagedSessionHandler(SessionFactory sessionFactory, Session delegate)
{
super();
this.delegate = delegate;
this.sessionFactory = sessionFactory;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
if (method.getName().equals("getSessionFactory"))
{
return sessionFactory;
}
else if (method.getName().equals("equals"))
{
// Only consider equal when proxies are identical.
return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
}
else if (method.getName().equals("hashCode"))
{
// Use hashCode of Session proxy.
return new Integer(hashCode());
}
if (method.getName().equals("isOpen"))
{
return delegate.isOpen() && !closed;
}
if (!delegate.isOpen())
{
// Defer to delegate error if it's closed.
try
{
return method.invoke(delegate, args);
}
catch (InvocationTargetException ex)
{
throw ex.getTargetException();
}
}
if (closed)
{
throw new IllegalStateException("This Session is closed.");
}
if (method.getName().equals("close"))
{
log.debug("Closing Session Proxy.");
delegate.disconnect();
closed = true;
return null;
}
try
{
return method.invoke(delegate, args);
}
catch (InvocationTargetException ex)
{
throw ex.getTargetException();
}
}
}
}