diff --git a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/ConfigurableJtaPlatform.java b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/ConfigurableJtaPlatform.java index 4eaa935fc1..4f394057f8 100644 --- a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/ConfigurableJtaPlatform.java +++ b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/ConfigurableJtaPlatform.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -16,60 +16,137 @@ package org.springframework.orm.hibernate4; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import javax.transaction.Status; +import javax.transaction.Synchronization; +import javax.transaction.SystemException; +import javax.transaction.Transaction; import javax.transaction.TransactionManager; +import javax.transaction.TransactionSynchronizationRegistry; import javax.transaction.UserTransaction; -import org.hibernate.service.jta.platform.internal.AbstractJtaPlatform; +import org.hibernate.TransactionException; +import org.hibernate.service.Service; import org.springframework.transaction.jta.UserTransactionAdapter; import org.springframework.util.Assert; /** - * Implementation of Hibernate 4's {@link org.hibernate.service.jta.platform.spi.JtaPlatform} - * SPI, exposing passed-in {@link TransactionManager} and {@link UserTransaction} references. + * Implementation of Hibernate 4's JtaPlatform SPI (which has a different package + * location in Hibernate 4.0-4.2 vs 4.3), exposing passed-in {@link TransactionManager}, + * {@link UserTransaction} and {@link TransactionSynchronizationRegistry} references. * * @author Juergen Hoeller * @since 3.1.2 */ -@SuppressWarnings("serial") -class ConfigurableJtaPlatform extends AbstractJtaPlatform { +@SuppressWarnings({"serial", "unchecked"}) +class ConfigurableJtaPlatform implements InvocationHandler { + + static final Class extends Service> jtaPlatformClass; + + static { + Class> jpClass; + try { + // Try Hibernate 4.0-4.2 JtaPlatform variant + jpClass = SpringSessionContext.class.getClassLoader().loadClass( + "org.hibernate.service.jta.platform.spi.JtaPlatform"); + } + catch (ClassNotFoundException ex) { + try { + // Try Hibernate 4.3 JtaPlatform variant + jpClass = SpringSessionContext.class.getClassLoader().loadClass( + "org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform"); + } + catch (ClassNotFoundException ex2) { + throw new IllegalStateException("Neither Hibernate 4.0-4.2 nor 4.3 variant of JtaPlatform found"); + } + } + jtaPlatformClass = (Class extends Service>) jpClass; + } + + static String getJtaPlatformBasePackage() { + String className = jtaPlatformClass.getName(); + return className.substring(0, className.length() - "spi.JtaPlatform".length()); + } + private final TransactionManager transactionManager; private final UserTransaction userTransaction; + private final TransactionSynchronizationRegistry transactionSynchronizationRegistry; + /** * Create a new ConfigurableJtaPlatform instance with the given * JTA TransactionManager and optionally a given UserTransaction. * @param tm the JTA TransactionManager reference (required) * @param ut the JTA UserTransaction reference (optional) + * @param tsr the JTA 1.1 TransactionSynchronizationRegistry (optional) */ - public ConfigurableJtaPlatform(TransactionManager tm, UserTransaction ut) { + public ConfigurableJtaPlatform(TransactionManager tm, UserTransaction ut, TransactionSynchronizationRegistry tsr) { Assert.notNull(tm, "TransactionManager reference must not be null"); this.transactionManager = tm; this.userTransaction = (ut != null ? ut : new UserTransactionAdapter(tm)); + this.transactionSynchronizationRegistry = tsr; } - @Override - protected TransactionManager locateTransactionManager() { + public TransactionManager retrieveTransactionManager() { return this.transactionManager; } - @Override - protected UserTransaction locateUserTransaction() { + public UserTransaction retrieveUserTransaction() { return this.userTransaction; } - @Override - protected boolean canCacheTransactionManager() { - return true; + public Object getTransactionIdentifier(Transaction transaction) { + return transaction; } + public boolean canRegisterSynchronization() { + try { + return (this.transactionManager.getStatus() == Status.STATUS_ACTIVE); + } + catch (SystemException ex) { + throw new TransactionException("Could not determine JTA transaction status", ex); + } + } + + public void registerSynchronization(Synchronization synchronization) { + if (this.transactionSynchronizationRegistry != null) { + this.transactionSynchronizationRegistry.registerInterposedSynchronization(synchronization); + } + else { + try { + this.transactionManager.getTransaction().registerSynchronization(synchronization); + } + catch (Exception ex) { + throw new TransactionException("Could not access JTA Transaction to register synchronization", ex); + } + } + } + + public int getCurrentStatus() throws SystemException { + return this.transactionManager.getStatus(); + } + + @Override - protected boolean canCacheUserTransaction() { - return true; + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Method targetMethod = getClass().getMethod(method.getName(), method.getParameterTypes()); + return targetMethod.invoke(this, args); + } + + /** + * Obtain a proxy that implements the current Hibernate version's JtaPlatform interface + * in the right package location, delegating all invocations to the same-named methods + * on this ConfigurableJtaPlatform class itself. + */ + public Object getJtaPlatformProxy() { + return Proxy.newProxyInstance(getClass().getClassLoader(), new Class>[] {jtaPlatformClass}, this); } } diff --git a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/LocalSessionFactoryBuilder.java b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/LocalSessionFactoryBuilder.java index 859935bad9..3854081bde 100644 --- a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/LocalSessionFactoryBuilder.java +++ b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/LocalSessionFactoryBuilder.java @@ -33,7 +33,6 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; import org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory; -import org.hibernate.service.jta.platform.internal.WebSphereExtendedJtaPlatform; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; @@ -140,7 +139,7 @@ public class LocalSessionFactoryBuilder extends Configuration { * instructing Hibernate to interact with externally managed transactions. *
A passed-in Spring {@link JtaTransactionManager} needs to contain a JTA * {@link TransactionManager} reference to be usable here, except for the WebSphere - * case where we'll automatically set {@link WebSphereExtendedJtaPlatform} accordingly. + * case where we'll automatically set {@code WebSphereExtendedJtaPlatform} accordingly. *
Note: If this is set, the Hibernate settings should not contain a JTA platform * setting to avoid meaningless double configuration. */ @@ -149,7 +148,8 @@ public class LocalSessionFactoryBuilder extends Configuration { if (jtaTransactionManager instanceof JtaTransactionManager) { boolean webspherePresent = ClassUtils.isPresent("com.ibm.wsspi.uow.UOWManager", getClass().getClassLoader()); if (webspherePresent) { - getProperties().put(AvailableSettings.JTA_PLATFORM, new WebSphereExtendedJtaPlatform()); + getProperties().put(AvailableSettings.JTA_PLATFORM, + ConfigurableJtaPlatform.getJtaPlatformBasePackage() + "internal.WebSphereExtendedJtaPlatform"); } else { JtaTransactionManager jtaTm = (JtaTransactionManager) jtaTransactionManager; @@ -158,12 +158,13 @@ public class LocalSessionFactoryBuilder extends Configuration { "Can only apply JtaTransactionManager which has a TransactionManager reference set"); } getProperties().put(AvailableSettings.JTA_PLATFORM, - new ConfigurableJtaPlatform(jtaTm.getTransactionManager(), jtaTm.getUserTransaction())); + new ConfigurableJtaPlatform(jtaTm.getTransactionManager(), jtaTm.getUserTransaction(), + jtaTm.getTransactionSynchronizationRegistry()).getJtaPlatformProxy()); } } else if (jtaTransactionManager instanceof TransactionManager) { getProperties().put(AvailableSettings.JTA_PLATFORM, - new ConfigurableJtaPlatform((TransactionManager) jtaTransactionManager, null)); + new ConfigurableJtaPlatform((TransactionManager) jtaTransactionManager, null, null).getJtaPlatformProxy()); } else { throw new IllegalArgumentException( diff --git a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/SessionFactoryUtils.java b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/SessionFactoryUtils.java index fc464074e6..de372bcf60 100644 --- a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/SessionFactoryUtils.java +++ b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/SessionFactoryUtils.java @@ -16,6 +16,7 @@ package org.springframework.orm.hibernate4; +import java.lang.reflect.Method; import javax.sql.DataSource; import org.apache.commons.logging.Log; @@ -45,7 +46,7 @@ import org.hibernate.exception.DataException; import org.hibernate.exception.JDBCConnectionException; import org.hibernate.exception.LockAcquisitionException; import org.hibernate.exception.SQLGrammarException; -import org.hibernate.service.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.service.spi.Wrapped; import org.springframework.dao.CannotAcquireLockException; import org.springframework.dao.DataAccessException; @@ -57,6 +58,8 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.dao.PessimisticLockingFailureException; import org.springframework.jdbc.datasource.DataSourceUtils; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; /** * Helper class featuring methods for Hibernate Session handling. @@ -83,6 +86,12 @@ public abstract class SessionFactoryUtils { static final Log logger = LogFactory.getLog(SessionFactoryUtils.class); + /** + * Bridging between the different ConnectionProvider package location in 4.0-4.2 vs 4.3. + */ + private static final Method getConnectionProviderMethod = + ClassUtils.getMethodIfAvailable(SessionFactoryImplementor.class, "getConnectionProvider"); + /** * Determine the DataSource of the given SessionFactory. @@ -91,8 +100,8 @@ public abstract class SessionFactoryUtils { * @see org.hibernate.engine.spi.SessionFactoryImplementor#getConnectionProvider */ public static DataSource getDataSource(SessionFactory sessionFactory) { - if (sessionFactory instanceof SessionFactoryImplementor) { - ConnectionProvider cp = ((SessionFactoryImplementor) sessionFactory).getConnectionProvider(); + if (getConnectionProviderMethod != null && sessionFactory instanceof SessionFactoryImplementor) { + Wrapped cp = (Wrapped) ReflectionUtils.invokeMethod(getConnectionProviderMethod, sessionFactory); return cp.unwrap(DataSource.class); } return null; diff --git a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/SpringSessionContext.java b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/SpringSessionContext.java index 1dfd5aa866..35180d2e3c 100644 --- a/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/SpringSessionContext.java +++ b/spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/SpringSessionContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -16,16 +16,16 @@ package org.springframework.orm.hibernate4; -import javax.transaction.TransactionManager; +import java.lang.reflect.Method; import org.hibernate.FlushMode; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.context.spi.CurrentSessionContext; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.service.jta.platform.spi.JtaPlatform; import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.util.ReflectionUtils; /** * Implementation of Hibernate 3.1's CurrentSessionContext interface @@ -44,7 +44,7 @@ public class SpringSessionContext implements CurrentSessionContext { private final SessionFactoryImplementor sessionFactory; - private final CurrentSessionContext jtaSessionContext; + private CurrentSessionContext jtaSessionContext; /** @@ -53,9 +53,17 @@ public class SpringSessionContext implements CurrentSessionContext { */ public SpringSessionContext(SessionFactoryImplementor sessionFactory) { this.sessionFactory = sessionFactory; - JtaPlatform jtaPlatform = sessionFactory.getServiceRegistry().getService(JtaPlatform.class); - TransactionManager transactionManager = jtaPlatform.retrieveTransactionManager(); - this.jtaSessionContext = (transactionManager != null ? new SpringJtaSessionContext(sessionFactory) : null); + try { + Object jtaPlatform = sessionFactory.getServiceRegistry().getService(ConfigurableJtaPlatform.jtaPlatformClass); + Method rtmMethod = ConfigurableJtaPlatform.jtaPlatformClass.getMethod("retrieveTransactionManager"); + Object transactionManager = ReflectionUtils.invokeMethod(rtmMethod, jtaPlatform); + if (transactionManager != null) { + this.jtaSessionContext = new SpringJtaSessionContext(sessionFactory); + } + } + catch (Exception ex) { + throw new IllegalStateException("Could not introspect Hibernate JtaPlatform for SpringJtaSessionContext", ex); + } } @@ -79,7 +87,7 @@ public class SpringSessionContext implements CurrentSessionContext { // Switch to FlushMode.AUTO, as we have to assume a thread-bound Session // with FlushMode.MANUAL, which needs to allow flushing within the transaction. FlushMode flushMode = session.getFlushMode(); - if (FlushMode.isManualFlushMode(flushMode) && + if (flushMode.equals(FlushMode.MANUAL) && !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { session.setFlushMode(FlushMode.AUTO); sessionHolder.setPreviousFlushMode(flushMode); diff --git a/spring-orm-hibernate4/src/test/java/org/springframework/orm/hibernate4/HibernateTransactionManagerTests.java b/spring-orm-hibernate4/src/test/java/org/springframework/orm/hibernate4/HibernateTransactionManagerTests.java index f235fc196e..b14a47f2f4 100644 --- a/spring-orm-hibernate4/src/test/java/org/springframework/orm/hibernate4/HibernateTransactionManagerTests.java +++ b/spring-orm-hibernate4/src/test/java/org/springframework/orm/hibernate4/HibernateTransactionManagerTests.java @@ -24,6 +24,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Properties; import javax.sql.DataSource; +import javax.transaction.TransactionManager; +import javax.transaction.TransactionSynchronizationRegistry; +import javax.transaction.UserTransaction; import org.hibernate.FlushMode; import org.hibernate.Interceptor; @@ -32,8 +35,10 @@ import org.hibernate.Session; import org.hibernate.SessionBuilder; import org.hibernate.SessionFactory; import org.hibernate.Transaction; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.HSQLDialect; import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory; import org.hibernate.exception.ConstraintViolationException; import org.junit.After; import org.junit.Test; @@ -49,6 +54,7 @@ import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.UnexpectedRollbackException; +import org.springframework.transaction.jta.JtaTransactionManager; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionSynchronizationManager; @@ -1180,6 +1186,37 @@ public class HibernateTransactionManagerTests { verify(session).close(); } + @Test + public void testSetJtaTransactionManager() throws Exception { + DataSource ds = mock(DataSource.class); + TransactionManager tm = mock(TransactionManager.class); + UserTransaction ut = mock(UserTransaction.class); + TransactionSynchronizationRegistry tsr = mock(TransactionSynchronizationRegistry.class); + JtaTransactionManager jtm = new JtaTransactionManager(); + jtm.setTransactionManager(tm); + jtm.setUserTransaction(ut); + jtm.setTransactionSynchronizationRegistry(tsr); + LocalSessionFactoryBuilder lsfb = new LocalSessionFactoryBuilder(ds); + lsfb.setJtaTransactionManager(jtm); + Object jtaPlatform = lsfb.getProperties().get(AvailableSettings.JTA_PLATFORM); + assertNotNull(jtaPlatform); + assertSame(tm, jtaPlatform.getClass().getMethod("retrieveTransactionManager").invoke(jtaPlatform)); + assertSame(ut, jtaPlatform.getClass().getMethod("retrieveUserTransaction").invoke(jtaPlatform)); + assertTrue(lsfb.getProperties().get(AvailableSettings.TRANSACTION_STRATEGY) instanceof CMTTransactionFactory); + } + + @Test + public void testSetTransactionManager() throws Exception { + DataSource ds = mock(DataSource.class); + TransactionManager tm = mock(TransactionManager.class); + LocalSessionFactoryBuilder lsfb = new LocalSessionFactoryBuilder(ds); + lsfb.setJtaTransactionManager(tm); + Object jtaPlatform = lsfb.getProperties().get(AvailableSettings.JTA_PLATFORM); + assertNotNull(jtaPlatform); + assertSame(tm, jtaPlatform.getClass().getMethod("retrieveTransactionManager").invoke(jtaPlatform)); + assertTrue(lsfb.getProperties().get(AvailableSettings.TRANSACTION_STRATEGY) instanceof CMTTransactionFactory); + } + public interface ImplementingSession extends Session, SessionImplementor { }