From b046285b7084958cc2aae7a15912170ab1f1b4ce Mon Sep 17 00:00:00 2001 From: sbohlen Date: Tue, 5 Jul 2011 12:18:21 +0000 Subject: [PATCH] SPRNET-1410 , SPRNET-1438 - properly handle zombied Transaction (Tx without a valid IDbConnection) on Rollback - mod to underlying AbstractPlatformTransactionManager to set default Transaction Sync to TransactionSynchronization.Never --- ....ClassLibraryQuickStart.4.5.resharper.user | 32 - .../NHibernate/HibernateTransactionManager.cs | 1857 ++++++------- .../Data/NHibernate/SessionFactoryUtils.cs | 2 +- .../HibernateTxScopeTransactionManager.cs | 23 +- .../Data/Core/TxScopeTransactionManager.cs | 570 ++-- .../AbstractPlatformTransactionManager.cs | 2429 +++++++++-------- .../TransactionSynchronizationManager.cs | 980 +++---- .../HibernateTransactionManagerTests.cs | 107 +- ...NHibernate30.Integration.Tests.2010.csproj | 215 ++ .../AdoPlatformTransactionManagerTests.cs | 48 +- .../ServiceDomainTransactionManagerTests.cs | 12 +- ...ScopeTransactionManagerIntegrationTests.cs | 6 + .../Core/TxScopeTransactionManagerTests.cs | 12 +- ...AbstractPlatformTransactionManagerTests.cs | 401 +-- .../TestSupport/WebApplicationTests.cs | 28 +- 15 files changed, 3561 insertions(+), 3161 deletions(-) delete mode 100644 dev-support/vs.net-2008/templates/solution/AdoSolution/App.AdoQuickStart/solution-items/App.ClassLibraryQuickStart/App.ClassLibraryQuickStart.4.5.resharper.user create mode 100644 test/Spring/Spring.Data.NHibernate30.Integration.Tests/Spring.Data.NHibernate30.Integration.Tests.2010.csproj diff --git a/dev-support/vs.net-2008/templates/solution/AdoSolution/App.AdoQuickStart/solution-items/App.ClassLibraryQuickStart/App.ClassLibraryQuickStart.4.5.resharper.user b/dev-support/vs.net-2008/templates/solution/AdoSolution/App.AdoQuickStart/solution-items/App.ClassLibraryQuickStart/App.ClassLibraryQuickStart.4.5.resharper.user deleted file mode 100644 index 292ed46a..00000000 --- a/dev-support/vs.net-2008/templates/solution/AdoSolution/App.AdoQuickStart/solution-items/App.ClassLibraryQuickStart/App.ClassLibraryQuickStart.4.5.resharper.user +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - False - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Spring/Spring.Data.NHibernate/Data/NHibernate/HibernateTransactionManager.cs b/src/Spring/Spring.Data.NHibernate/Data/NHibernate/HibernateTransactionManager.cs index 1141f27e..dadaa52c 100644 --- a/src/Spring/Spring.Data.NHibernate/Data/NHibernate/HibernateTransactionManager.cs +++ b/src/Spring/Spring.Data.NHibernate/Data/NHibernate/HibernateTransactionManager.cs @@ -1,926 +1,931 @@ -#region License - -/* - * Copyright © 2002-2011 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. - */ - -#endregion - -#region Imports - -using System; -using System.Data; -using System.Reflection; - -using NHibernate; -using NHibernate.Transaction; - -using Spring.Core.TypeResolution; -using Spring.Dao; -using Spring.Data.Common; -using Spring.Data.Support; -using Spring.Objects.Factory; -using Spring.Transaction; -using Spring.Transaction.Support; -using Spring.Util; -using HibernateTransactionException = NHibernate.TransactionException; - -#endregion - -namespace Spring.Data.NHibernate -{ - /// - /// PlatformTransactionManager implementation for a single Hibernate SessionFactory. - /// Binds a Hibernate Session from the specified factory to the thread, potentially - /// allowing for one thread Session per factory - /// - /// - /// SessionFactoryUtils and HibernateTemplate are aware of thread-bound Sessions and participate in such - /// transactions automatically. Using either of those is required for Hibernate - /// access code that needs to support this transaction handling mechanism. - /// - /// Supports custom isolation levels at the start of the transaction - /// , and timeouts that get applied as appropriate - /// Hibernate query timeouts. To support the latter, application code must either use - /// HibernateTemplate (which by default applies the timeouts) or call - /// SessionFactoryUtils.applyTransactionTimeout for each created - /// Hibernate Query object. - /// - /// Note that you can specify a Spring IDbProvider instance which if shared with - /// a corresponding instance of AdoTemplate will allow for mixing ADO.NET/NHibernate - /// operations within a single transaction. - /// - /// Mark Pollack (.NET) - public class HibernateTransactionManager : AbstractPlatformTransactionManager, IResourceTransactionManager, IObjectFactoryAware, IInitializingObject - { - #region Fields - - private ISessionFactory sessionFactory; - - private IDbProvider dbProvider; - - private bool autodetectDbProvider = true; - - private Object entityInterceptor; - - private IAdoExceptionTranslator adoExceptionTranslator; - - private IAdoExceptionTranslator defaultExceptionTranslator; - - /// - /// Just needed for entityInterceptorBeanName. - /// - private IObjectFactory objectFactory; - - #endregion - - #region Constructor (s) - - /// - /// Initializes a new instance of the class. - /// - public HibernateTransactionManager() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The session factory. - public HibernateTransactionManager(ISessionFactory sessionFactory) - { - this.sessionFactory = sessionFactory; - AfterPropertiesSet(); - } - - #endregion - - #region Properties - - /// - /// Gets or sets the db provider. - /// - /// The db provider. - public IDbProvider DbProvider - { - get { return dbProvider; } - set { dbProvider = value; } - } - - /// - /// Gets or sets a Hibernate entity interceptor that allows to inspect and change - /// property values before writing to and reading from the database. - /// When getting, return the current Hibernate entity interceptor, or null if none. - /// - /// The entity interceptor. - /// - /// Resolves an entity interceptor object name via the object factory, - /// if necessary. - /// Will get applied to any new Session created by this transaction manager. - /// Such an interceptor can either be set at the SessionFactory level, - /// i.e. on LocalSessionFactoryObject, or at the Session level, i.e. on - /// HibernateTemplate, HibernateInterceptor, and HibernateTransactionManager. - /// It's preferable to set it on LocalSessionFactoryObject or HibernateTransactionManager - /// to avoid repeated configuration and guarantee consistent behavior in transactions. - /// - /// If object factory is null and need to get entity interceptor via object name. - public IInterceptor EntityInterceptor - { - get - { - if (this.entityInterceptor is IInterceptor) - { - return (IInterceptor) entityInterceptor; - } - else if (this.entityInterceptor is string) - { - if (this.objectFactory == null) - { - throw new InvalidOperationException ("Cannot get entity interceptor via object name if no object factory set"); - } - String objectName = (String) this.entityInterceptor; - return (IInterceptor) this.objectFactory.GetObject(objectName, typeof(IInterceptor)); - } - else - { - return null; - } - } - set - { - entityInterceptor = value; - } - } - - /// - /// Sets the object name of a Hibernate entity interceptor that - /// allows to inspect and change property values before writing to and reading from the database. - /// - /// The name of the entity interceptor object. - /// - /// Will get applied to any new Session created by this transaction manager. - ///

Requires the object factory to be known, to be able to resolve the object - /// name to an interceptor instance on session creation. Typically used for - /// prototype interceptors, i.e. a new interceptor instance per session. - ///

- ///

Can also be used for shared interceptor instances, but it is recommended - /// to set the interceptor reference directly in such a scenario. - ///

- ///
- public string EntityInterceptorObjectName - { - set - { - entityInterceptor = value; - } - } - - /// - /// Gets or sets the ADO.NET exception translator for this transaction manager. - /// - /// - /// Applied to ADO.NET Exceptions (wrapped by Hibernate's ADOException) - /// - /// The ADO exception translator. - public IAdoExceptionTranslator AdoExceptionTranslator - { - get { return adoExceptionTranslator; } - set { adoExceptionTranslator = value; } - } - - /// - /// Gets the default IAdoException translator, lazily creating it if nece - /// - /// The default IAdoException translator. - public IAdoExceptionTranslator DefaultAdoExceptionTranslator - { - get - { lock(this) - { - if (defaultExceptionTranslator == null) - { - if (dbProvider != null) - { - defaultExceptionTranslator = new ErrorCodeExceptionTranslator(dbProvider); - } - else - { - defaultExceptionTranslator = SessionFactoryUtils.NewAdoExceptionTranslator(SessionFactory); - } - } - return defaultExceptionTranslator; - } - } - } - - /// - /// Gets or sets the SessionFactory that this instance should manage transactions for. - /// - /// The session factory. - public ISessionFactory SessionFactory - { - get { return sessionFactory; } - set { sessionFactory = value; } - } - - - /// - /// Gets the resource factory that this transaction manager operates on, - /// For the HibenratePlatformTransactionManager this the SessionFactory - /// - /// The SessionFactory. - public object ResourceFactory - { - get { return sessionFactory; } - } - - /// - /// Set whether to autodetect a ADO.NET connection used by the Hibernate SessionFactory, - /// if set via LocalSessionFactoryObject's DbProvider. Default is "true". - /// - /// - /// true if [autodetect data source]; otherwise, false. - /// - /// - ///

Can be turned off to deliberately ignore an available IDbProvider, - /// to not expose Hibernate transactions as ADO.NET transactions for that IDbProvider. - ///

- ///
- public bool AutodetectDbProvider - { - set { autodetectDbProvider = value; } - } - - #endregion - - #region Methods - - #endregion - - - /// - /// The object factory just needs to be known for resolving entity interceptor - /// It does not need to be set for any other mode of operation. - /// - /// - /// Owning - /// (may not be ). The object can immediately - /// call methods on the factory. - /// - public IObjectFactory ObjectFactory - { - set - { - objectFactory = value; - } - } - - /// - /// Return the current transaction object. - /// - /// The current transaction object. - /// - /// If transaction support is not available. - /// - /// - /// In the case of lookup or system errors. - /// - protected override object DoGetTransaction() - { - HibernateTransactionObject txObject = new HibernateTransactionObject(); - txObject.SavepointAllowed = NestedTransactionsAllowed; - if (TransactionSynchronizationManager.HasResource(SessionFactory)) - { - SessionHolder sessionHolder = - (SessionHolder) TransactionSynchronizationManager.GetResource(SessionFactory); - if (log.IsDebugEnabled) - { - log.Debug("Found thread-bound Session [" + sessionHolder.Session + - "] for Hibernate transaction"); - } - txObject.SetSessionHolder(sessionHolder, false); - if (DbProvider != null) - { - ConnectionHolder conHolder = (ConnectionHolder) - TransactionSynchronizationManager.GetResource(DbProvider); - txObject.ConnectionHolder = conHolder; - } - } - return txObject; - } - - /// - /// Check if the given transaction object indicates an existing, - /// i.e. already begun, transaction. - /// - /// - /// Transaction object returned by - /// . - /// - /// True if there is an existing transaction. - /// - /// In the case of system errors. - /// - protected override bool IsExistingTransaction(object transaction) - { - return ((HibernateTransactionObject) transaction).HasTransaction(); - } - - /// - /// Begin a new transaction with the given transaction definition. - /// - /// - /// Transaction object returned by - /// . - /// - /// - /// instance, describing - /// propagation behavior, isolation level, timeout etc. - /// - /// - /// Does not have to care about applying the propagation behavior, - /// as this has already been handled by this abstract manager. - /// - /// - /// In the case of creation or system errors. - /// - protected override void DoBegin(object transaction, ITransactionDefinition definition) - { - HibernateTransactionObject txObject = (HibernateTransactionObject) transaction; - - if (DbProvider != null && TransactionSynchronizationManager.HasResource(DbProvider) - && !txObject.ConnectionHolder.SynchronizedWithTransaction) - { - throw new IllegalTransactionStateException( - "Pre-bound ADO.NET Connection found - HibernateTransactionManager does not support " + - "running within AdoTransactionManager if told to manage the DbProvider itself. " + - "It is recommended to use a single HibernateTransactionManager for all transactions " + - "on a single DbProvider, no matter whether Hibernate or ADO.NET access."); - } - ISession session = null; - try - { - - if (txObject.SessionHolder == null || txObject.SessionHolder.SynchronizedWithTransaction) - { - IInterceptor interceptor = EntityInterceptor; - ISession newSession = (interceptor != null ? - SessionFactory.OpenSession(interceptor) : SessionFactory.OpenSession()); - - if (log.IsDebugEnabled) - { - log.Debug("Opened new Session [" + newSession + "] for Hibernate transaction"); - } - txObject.SetSessionHolder(new SessionHolder(newSession), true); - - } - txObject.SessionHolder.SynchronizedWithTransaction = true; - session = txObject.SessionHolder.Session; - - IDbConnection con = session.Connection; - //TODO isolation level mgmt - //IsolationLevel previousIsolationLevel = - - if (definition.ReadOnly && txObject.NewSessionHolder) - { - // Just set to NEVER in case of a new Session for this transaction. - session.FlushMode = FlushMode.Never; - } - - if (!definition.ReadOnly && !txObject.NewSessionHolder) - { - // We need AUTO or COMMIT for a non-read-only transaction. - FlushMode flushMode = session.FlushMode; - if (FlushMode.Never == flushMode) - { - session.FlushMode = FlushMode.Auto; - txObject.SessionHolder.PreviousFlushMode = flushMode; - } - } - - // Add the Hibernate transaction to the session holder. - // for now pass in tx options isolation level. - ITransaction hibernateTx = session.BeginTransaction(definition.TransactionIsolationLevel); - IDbTransaction adoTx = GetIDbTransaction(hibernateTx); - - // Add the Hibernate transaction to the session holder. - txObject.SessionHolder.Transaction = hibernateTx; - - // Register transaction timeout. - int timeout = DetermineTimeout(definition); - if (timeout != DefaultTransactionDefinition.TIMEOUT_DEFAULT) - { - txObject.SessionHolder.TimeoutInSeconds = timeout; - } - - // Register the Hibernate Session's ADO.NET Connection/TX pair for the DbProvider, if set. - if (DbProvider != null) - { - //investigate passing null for tx. - ConnectionHolder conHolder = new ConnectionHolder(con, adoTx); - if (timeout != DefaultTransactionDefinition.TIMEOUT_DEFAULT) - { - conHolder.TimeoutInMillis = definition.TransactionTimeout; - } - if (log.IsDebugEnabled) - { - log.Debug("Exposing Hibernate transaction as ADO transaction [" + con + "]"); - } - TransactionSynchronizationManager.BindResource(DbProvider, conHolder); - txObject.ConnectionHolder = conHolder; - } - - // Bind the session holder to the thread. - if (txObject.NewSessionHolder) - { - TransactionSynchronizationManager.BindResource(SessionFactory, txObject.SessionHolder); - } - - } catch (Exception ex) - { - SessionFactoryUtils.CloseSession(session); - throw new CannotCreateTransactionException("Could not open Hibernate Session for transaction", ex); - } - - - } - - - - - /// - /// Suspend the resources of the current transaction. - /// - /// - /// Transaction object returned by - /// . - /// - /// - /// An object that holds suspended resources (will be kept unexamined for passing it into - /// .) - /// - /// - /// Transaction synchronization will already have been suspended. - /// - /// - /// If suspending is not supported by the transaction manager implementation. - /// - /// - /// in case of system errors. - /// - protected override object DoSuspend(object transaction) - { - HibernateTransactionObject txObject = (HibernateTransactionObject) transaction; - txObject.SetSessionHolder(null, false); - SessionHolder sessionHolder = - (SessionHolder) TransactionSynchronizationManager.UnbindResource(SessionFactory); - ConnectionHolder connectionHolder = null; - if (DbProvider != null) - { - connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.UnbindResource(DbProvider); - } - return new SuspendedResourcesHolder(sessionHolder, connectionHolder); - - } - - /// - /// Resume the resources of the current transaction. - /// - /// - /// Transaction object returned by - /// . - /// - /// - /// The object that holds suspended resources as returned by - /// . - /// - /// - /// Transaction synchronization will be resumed afterwards. - /// - /// - /// If suspending is not supported by the transaction manager implementation. - /// - /// - /// In the case of system errors. - /// - protected override void DoResume(object transaction, object suspendedResources) - { - SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources; - if (TransactionSynchronizationManager.HasResource(SessionFactory)) - { - // From non-transactional code running in active transaction synchronization - // -> can be safely removed, will be closed on transaction completion. - TransactionSynchronizationManager.UnbindResource(SessionFactory); - } - TransactionSynchronizationManager.BindResource(SessionFactory, resourcesHolder.SessionHolder); - if (DbProvider != null) - { - TransactionSynchronizationManager.BindResource(DbProvider, resourcesHolder.ConnectionHolder); - } - } - - /// - /// Perform an actual commit on the given transaction. - /// - /// The status representation of the transaction. - /// - ///

- /// An implementation does not need to check the rollback-only flag. - ///

- ///
- /// - /// In the case of system errors. - /// - protected override void DoCommit(DefaultTransactionStatus status) - { - HibernateTransactionObject txObject = (HibernateTransactionObject) status.Transaction; - if (status.Debug) - { - log.Debug("Committing Hibernate transaction on Session [" + - txObject.SessionHolder.Session + "]"); - } - try - { - txObject.SessionHolder.Transaction.Commit(); - } - // Note, unfortunate collision of namespaces/classname for NHibernate.TransactionException - // and Spring.Data.NHibernate requires this wierd construct. - catch (Exception ex) - { - Type nhibTxExceptiontype = TypeResolutionUtils.ResolveType("NHibernate.TransactionException, NHibernate"); - if (ex.GetType().Equals(nhibTxExceptiontype)) - { - // assumably from commit call to the underlying ADO.NET connection - throw new TransactionSystemException("Could not commit Hibernate transaction", ex); - } - HibernateException hibEx = ex as HibernateException; - if (hibEx != null) - { - // assumably failed to flush changes to database - throw ConvertHibernateAccessException(hibEx); - } - throw; - } - } - - /// - /// Perform an actual rollback on the given transaction. - /// - /// The status representation of the transaction. - /// - /// An implementation does not need to check the new transaction flag. - /// - /// - /// In the case of system errors. - /// - protected override void DoRollback(DefaultTransactionStatus status) - { - HibernateTransactionObject txObject = (HibernateTransactionObject) status.Transaction; - if (status.Debug) - { - log.Debug("Rolling back Hibernate transaction on Session [" + - txObject.SessionHolder.Session + "]"); - } - try - { - txObject.SessionHolder.Transaction.Rollback(); - } - catch (HibernateTransactionException ex) - { - throw new TransactionSystemException("Could not roll back Hibernate transaction", ex); - } - catch (HibernateException ex) - { - // Shouldn't really happen, as a rollback doesn't cause a flush. - throw ConvertHibernateAccessException(ex); - } - finally - { - if (!txObject.NewSessionHolder) - { - // Clear all pending inserts/updates/deletes in the Session. - // Necessary for pre-bound Sessions, to avoid inconsistent state. - txObject.SessionHolder.Session.Clear(); - } - } - - - - } - - - /// - /// Set the given transaction rollback-only. Only called on rollback - /// if the current transaction takes part in an existing one. - /// - /// The status representation of the transaction. - /// - /// In the case of system errors. - /// - protected override void DoSetRollbackOnly(DefaultTransactionStatus status) - { - HibernateTransactionObject txObject = (HibernateTransactionObject) status.Transaction; - if (status.Debug) - { - log.Debug("Setting Hibernate transaction on Session [" + - txObject.SessionHolder.Session + "] rollback-only"); - } - txObject.SetRollbackOnly(); - } - - - /// - /// Gets the ADO.NET IDbTransaction object from the NHibernate ITransaction object. - /// - /// The hibernate transaction. - /// The ADO.NET transaction. Null if could not get the transaction. Warning - /// messages will be logged in that case. - protected IDbTransaction GetIDbTransaction(ITransaction hibernateTx) - { - AdoTransaction hibernateAdoTx = hibernateTx as AdoTransaction; - - IDbTransaction adoTransaction = null; - if (hibernateAdoTx != null) - { - try - { - FieldInfo fi = hibernateAdoTx.GetType().GetField("trans", BindingFlags.Instance | BindingFlags.NonPublic); - adoTransaction = fi.GetValue(hibernateAdoTx) as IDbTransaction; - } - catch (Exception e) - { - log.Warn("Could not extract IDbTransaction from Hibernate AdoTransaction using field name trans.", e); - } - } - else - { - log.Warn("Hibernate ITransaction not of expected type AdoTransaction. Could not extract IDbTransaction from Hibernate AdoTransaction."); - } - return adoTransaction; - } - - /// - /// Convert the given HibernateException to an appropriate exception from - /// the Spring.Dao hierarchy. Can be overridden in subclasses. - /// - /// The HibernateException that occured. - /// The corresponding DataAccessException instance - protected virtual DataAccessException ConvertHibernateAccessException(HibernateException ex) - { - if (AdoExceptionTranslator != null && ex is ADOException) - { - return ConvertAdoAccessException((ADOException) ex, AdoExceptionTranslator); - } - else if (ex is ADOException) - { - return ConvertAdoAccessException((ADOException)ex, DefaultAdoExceptionTranslator); - } - return SessionFactoryUtils.ConvertHibernateAccessException(ex); - } - - /// - /// Convert the given ADOException to an appropriate exception from the - /// the Spring.Dao hierarchy. Can be overridden in subclasses. - /// - /// The ADOException that occured, wrapping the underlying - /// ADO.NET thrown exception. - /// The translator to convert hibernate ADOExceptions. - /// - /// The corresponding DataAccessException instance - /// - protected virtual DataAccessException ConvertAdoAccessException(ADOException ex, IAdoExceptionTranslator translator) - { - return translator.Translate("Hibernate flusing: " + ex.Message, null, ex.InnerException); - } - - /// - /// Cleanup resources after transaction completion. - /// - /// Transaction object returned by - /// . - /// - /// - /// This implemenation unbinds the SessionFactory and - /// DbProvider from thread local storage and closes the - /// ISession. - /// - ///

- /// Called after - /// and - /// - /// execution on any outcome. - ///

- ///

- /// Should not throw any exceptions but just issue warnings on errors. - ///

- ///
- protected override void DoCleanupAfterCompletion( object transaction ) - { - HibernateTransactionObject txObject = (HibernateTransactionObject) transaction; - - // Remove the session holder from the thread. - if (txObject.NewSessionHolder) - { - TransactionSynchronizationManager.UnbindResource(SessionFactory); - } - // Remove the ADO.NET connection holder from the thread, if exposed. - if (DbProvider != null) - { - TransactionSynchronizationManager.UnbindResource(DbProvider); - } - /* - try - { - //TODO investigate isolation level settings... - //IDbConnection con = txObject.SessionHolder.Session.Connection; - //AdoUtils.ResetConnectionAfterTransaction(con, txObject.PreviousIsolationLevel); - } - catch (HibernateException ex) - { - log.Info("Could not access ADO.NET IDbConnection of Hibernate Session", ex); - } - */ - ISession session = txObject.SessionHolder.Session; - if (txObject.NewSessionHolder) - { - if (log.IsDebugEnabled) - { - log.Debug("Closing Hibernate Session [" + session + "] after transaction"); - } - SessionFactoryUtils.CloseSessionOrRegisterDeferredClose(session, SessionFactory); - } - else - { - if (log.IsDebugEnabled) - { - log.Debug("Not closing pre-bound Hibernate Session [" + session + "] after transaction"); - } - if (txObject.SessionHolder.AssignedPreviousFlushMode) - { - session.FlushMode = txObject.SessionHolder.PreviousFlushMode; - } - } - txObject.SessionHolder.Clear(); - - - } - - private class HibernateTransactionObject : AdoTransactionObjectSupport - { - - private SessionHolder sessionHolder; - - private bool newSessionHolder; - - - public void SetSessionHolder(SessionHolder sessionHolder, bool newSessionHolder) - { - this.sessionHolder = sessionHolder; - this.newSessionHolder = newSessionHolder; - } - - - public SessionHolder SessionHolder - { - get - { - return sessionHolder; - } - } - - public bool NewSessionHolder - { - get - { - return newSessionHolder; - } - } - - public bool HasTransaction() - { - return (this.sessionHolder != null && this.sessionHolder.Transaction != null); - } - - public void SetRollbackOnly() - { - SessionHolder.RollbackOnly = true; - if (ConnectionHolder != null) - { - ConnectionHolder.RollbackOnly = true; - } - } - - /// - /// Return whether the transaction is internally marked as rollback-only. - /// - /// - /// True of the transaction is marked as rollback-only. - public override bool RollbackOnly - { - get - { - return SessionHolder.RollbackOnly || - (ConnectionHolder != null && ConnectionHolder.RollbackOnly); - } - } - } - - private class SuspendedResourcesHolder - { - - private readonly SessionHolder sessionHolder; - - private readonly ConnectionHolder connectionHolder; - - public SuspendedResourcesHolder(SessionHolder sessionHolder, ConnectionHolder conHolder) - { - this.sessionHolder = sessionHolder; - this.connectionHolder = conHolder; - } - - public SessionHolder SessionHolder - { - get - { - return sessionHolder; - } - - } - - public ConnectionHolder ConnectionHolder - { - get - { - return connectionHolder; - } - - } - } - - /// - /// Invoked by an - /// after it has injected all of an object's dependencies. - /// - /// - ///

- /// This method allows the object instance to perform the kind of - /// initialization only possible when all of it's dependencies have - /// been injected (set), and to throw an appropriate exception in the - /// event of misconfiguration. - ///

- ///

- /// Please do consult the class level documentation for the - /// interface for a - /// description of exactly when this method is invoked. In - /// particular, it is worth noting that the - /// - /// and - /// callbacks will have been invoked prior to this method being - /// called. - ///

- ///
- /// - /// In the event of misconfiguration (such as the failure to set a - /// required property) or if initialization fails. - /// - public void AfterPropertiesSet() - { - if (SessionFactory == null) { - throw new ArgumentException("sessionFactory is required"); - } - if (this.entityInterceptor is string && this.objectFactory == null) { - throw new ArgumentException ("objectFactory is required for entityInterceptorBeanName"); - } - - // Try to derive a DbProvider given the SessionFactory. - if (this.autodetectDbProvider && DbProvider == null) { - IDbProvider sfDbProvider = SessionFactoryUtils.GetDbProvider(SessionFactory); - if (sfDbProvider != null) { - // Use the SessionFactory's DataSource for exposing transactions to ADO.NET code. - if (log.IsInfoEnabled) { - log.Info("Derived DbProvider [" + sfDbProvider.DbMetadata.ProductName + - "] of Hibernate SessionFactory for HibernateTransactionManager"); - } - DbProvider = sfDbProvider; - } - else - { - log.Info("Could not auto detect DbProvider from SessionFactory configuration"); - } - - } - } - } -} +#region License + +/* + * Copyright © 2002-2011 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. + */ + +#endregion + +#region Imports + +using System; +using System.Data; +using System.Reflection; + +using NHibernate; +using NHibernate.Transaction; + +using Spring.Core.TypeResolution; +using Spring.Dao; +using Spring.Data.Common; +using Spring.Data.Support; +using Spring.Objects.Factory; +using Spring.Transaction; +using Spring.Transaction.Support; +using Spring.Util; +using HibernateTransactionException = NHibernate.TransactionException; + +#endregion + +namespace Spring.Data.NHibernate +{ + /// + /// PlatformTransactionManager implementation for a single Hibernate SessionFactory. + /// Binds a Hibernate Session from the specified factory to the thread, potentially + /// allowing for one thread Session per factory + /// + /// + /// SessionFactoryUtils and HibernateTemplate are aware of thread-bound Sessions and participate in such + /// transactions automatically. Using either of those is required for Hibernate + /// access code that needs to support this transaction handling mechanism. + /// + /// Supports custom isolation levels at the start of the transaction + /// , and timeouts that get applied as appropriate + /// Hibernate query timeouts. To support the latter, application code must either use + /// HibernateTemplate (which by default applies the timeouts) or call + /// SessionFactoryUtils.applyTransactionTimeout for each created + /// Hibernate Query object. + /// + /// Note that you can specify a Spring IDbProvider instance which if shared with + /// a corresponding instance of AdoTemplate will allow for mixing ADO.NET/NHibernate + /// operations within a single transaction. + /// + /// Mark Pollack (.NET) + public class HibernateTransactionManager : AbstractPlatformTransactionManager, IResourceTransactionManager, IObjectFactoryAware, IInitializingObject + { + #region Fields + + private ISessionFactory sessionFactory; + + private IDbProvider dbProvider; + + private bool autodetectDbProvider = true; + + private Object entityInterceptor; + + private IAdoExceptionTranslator adoExceptionTranslator; + + private IAdoExceptionTranslator defaultExceptionTranslator; + + /// + /// Just needed for entityInterceptorBeanName. + /// + private IObjectFactory objectFactory; + + #endregion + + #region Constructor (s) + + /// + /// Initializes a new instance of the class. + /// + public HibernateTransactionManager() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The session factory. + public HibernateTransactionManager(ISessionFactory sessionFactory) + { + this.sessionFactory = sessionFactory; + AfterPropertiesSet(); + } + + #endregion + + #region Properties + + /// + /// Gets or sets the db provider. + /// + /// The db provider. + public IDbProvider DbProvider + { + get { return dbProvider; } + set { dbProvider = value; } + } + + /// + /// Gets or sets a Hibernate entity interceptor that allows to inspect and change + /// property values before writing to and reading from the database. + /// When getting, return the current Hibernate entity interceptor, or null if none. + /// + /// The entity interceptor. + /// + /// Resolves an entity interceptor object name via the object factory, + /// if necessary. + /// Will get applied to any new Session created by this transaction manager. + /// Such an interceptor can either be set at the SessionFactory level, + /// i.e. on LocalSessionFactoryObject, or at the Session level, i.e. on + /// HibernateTemplate, HibernateInterceptor, and HibernateTransactionManager. + /// It's preferable to set it on LocalSessionFactoryObject or HibernateTransactionManager + /// to avoid repeated configuration and guarantee consistent behavior in transactions. + /// + /// If object factory is null and need to get entity interceptor via object name. + public IInterceptor EntityInterceptor + { + get + { + if (this.entityInterceptor is IInterceptor) + { + return (IInterceptor)entityInterceptor; + } + else if (this.entityInterceptor is string) + { + if (this.objectFactory == null) + { + throw new InvalidOperationException("Cannot get entity interceptor via object name if no object factory set"); + } + String objectName = (String)this.entityInterceptor; + return (IInterceptor)this.objectFactory.GetObject(objectName, typeof(IInterceptor)); + } + else + { + return null; + } + } + set + { + entityInterceptor = value; + } + } + + /// + /// Sets the object name of a Hibernate entity interceptor that + /// allows to inspect and change property values before writing to and reading from the database. + /// + /// The name of the entity interceptor object. + /// + /// Will get applied to any new Session created by this transaction manager. + ///

Requires the object factory to be known, to be able to resolve the object + /// name to an interceptor instance on session creation. Typically used for + /// prototype interceptors, i.e. a new interceptor instance per session. + ///

+ ///

Can also be used for shared interceptor instances, but it is recommended + /// to set the interceptor reference directly in such a scenario. + ///

+ ///
+ public string EntityInterceptorObjectName + { + set + { + entityInterceptor = value; + } + } + + /// + /// Gets or sets the ADO.NET exception translator for this transaction manager. + /// + /// + /// Applied to ADO.NET Exceptions (wrapped by Hibernate's ADOException) + /// + /// The ADO exception translator. + public IAdoExceptionTranslator AdoExceptionTranslator + { + get { return adoExceptionTranslator; } + set { adoExceptionTranslator = value; } + } + + /// + /// Gets the default IAdoException translator, lazily creating it if nece + /// + /// The default IAdoException translator. + public IAdoExceptionTranslator DefaultAdoExceptionTranslator + { + get + { + lock (this) + { + if (defaultExceptionTranslator == null) + { + if (dbProvider != null) + { + defaultExceptionTranslator = new ErrorCodeExceptionTranslator(dbProvider); + } + else + { + defaultExceptionTranslator = SessionFactoryUtils.NewAdoExceptionTranslator(SessionFactory); + } + } + return defaultExceptionTranslator; + } + } + } + + /// + /// Gets or sets the SessionFactory that this instance should manage transactions for. + /// + /// The session factory. + public ISessionFactory SessionFactory + { + get { return sessionFactory; } + set { sessionFactory = value; } + } + + + /// + /// Gets the resource factory that this transaction manager operates on, + /// For the HibenratePlatformTransactionManager this the SessionFactory + /// + /// The SessionFactory. + public object ResourceFactory + { + get { return sessionFactory; } + } + + /// + /// Set whether to autodetect a ADO.NET connection used by the Hibernate SessionFactory, + /// if set via LocalSessionFactoryObject's DbProvider. Default is "true". + /// + /// + /// true if [autodetect data source]; otherwise, false. + /// + /// + ///

Can be turned off to deliberately ignore an available IDbProvider, + /// to not expose Hibernate transactions as ADO.NET transactions for that IDbProvider. + ///

+ ///
+ public bool AutodetectDbProvider + { + set { autodetectDbProvider = value; } + } + + #endregion + + #region Methods + + #endregion + + + /// + /// The object factory just needs to be known for resolving entity interceptor + /// It does not need to be set for any other mode of operation. + /// + /// + /// Owning + /// (may not be ). The object can immediately + /// call methods on the factory. + /// + public IObjectFactory ObjectFactory + { + set + { + objectFactory = value; + } + } + + /// + /// Return the current transaction object. + /// + /// The current transaction object. + /// + /// If transaction support is not available. + /// + /// + /// In the case of lookup or system errors. + /// + protected override object DoGetTransaction() + { + HibernateTransactionObject txObject = new HibernateTransactionObject(); + txObject.SavepointAllowed = NestedTransactionsAllowed; + if (TransactionSynchronizationManager.HasResource(SessionFactory)) + { + SessionHolder sessionHolder = + (SessionHolder)TransactionSynchronizationManager.GetResource(SessionFactory); + if (log.IsDebugEnabled) + { + log.Debug("Found thread-bound Session [" + sessionHolder.Session + + "] for Hibernate transaction"); + } + txObject.SetSessionHolder(sessionHolder, false); + if (DbProvider != null) + { + ConnectionHolder conHolder = (ConnectionHolder) + TransactionSynchronizationManager.GetResource(DbProvider); + txObject.ConnectionHolder = conHolder; + } + } + return txObject; + } + + /// + /// Check if the given transaction object indicates an existing, + /// i.e. already begun, transaction. + /// + /// + /// Transaction object returned by + /// . + /// + /// True if there is an existing transaction. + /// + /// In the case of system errors. + /// + protected override bool IsExistingTransaction(object transaction) + { + return ((HibernateTransactionObject)transaction).HasTransaction(); + } + + /// + /// Begin a new transaction with the given transaction definition. + /// + /// + /// Transaction object returned by + /// . + /// + /// + /// instance, describing + /// propagation behavior, isolation level, timeout etc. + /// + /// + /// Does not have to care about applying the propagation behavior, + /// as this has already been handled by this abstract manager. + /// + /// + /// In the case of creation or system errors. + /// + protected override void DoBegin(object transaction, ITransactionDefinition definition) + { + HibernateTransactionObject txObject = (HibernateTransactionObject)transaction; + + if (DbProvider != null && TransactionSynchronizationManager.HasResource(DbProvider) + && !txObject.ConnectionHolder.SynchronizedWithTransaction) + { + throw new IllegalTransactionStateException( + "Pre-bound ADO.NET Connection found - HibernateTransactionManager does not support " + + "running within AdoTransactionManager if told to manage the DbProvider itself. " + + "It is recommended to use a single HibernateTransactionManager for all transactions " + + "on a single DbProvider, no matter whether Hibernate or ADO.NET access."); + } + ISession session = null; + try + { + + + if (txObject.SessionHolder == null || txObject.SessionHolder.SynchronizedWithTransaction) + //if (txObject.SessionHolder == null || (txObject.SessionHolder.SynchronizedWithTransaction && TransactionSynchronizationManager.SynchronizationActive)) + //if (txObject.SessionHolder == null || (txObject.SessionHolder.SynchronizedWithTransaction && TransactionSynchronizationManager.ActualTransactionActive)) + { + IInterceptor interceptor = EntityInterceptor; + ISession newSession = (interceptor != null ? + SessionFactory.OpenSession(interceptor) : SessionFactory.OpenSession()); + + if (log.IsDebugEnabled) + { + log.Debug("Opened new Session [" + newSession + "] for Hibernate transaction"); + } + txObject.SetSessionHolder(new SessionHolder(newSession), true); + + } + txObject.SessionHolder.SynchronizedWithTransaction = true; + session = txObject.SessionHolder.Session; + + IDbConnection con = session.Connection; + //TODO isolation level mgmt + //IsolationLevel previousIsolationLevel = + + if (definition.ReadOnly && txObject.NewSessionHolder) + { + // Just set to NEVER in case of a new Session for this transaction. + session.FlushMode = FlushMode.Never; + } + + if (!definition.ReadOnly && !txObject.NewSessionHolder) + { + // We need AUTO or COMMIT for a non-read-only transaction. + FlushMode flushMode = session.FlushMode; + if (FlushMode.Never == flushMode) + { + session.FlushMode = FlushMode.Auto; + txObject.SessionHolder.PreviousFlushMode = flushMode; + } + } + + // Add the Hibernate transaction to the session holder. + // for now pass in tx options isolation level. + ITransaction hibernateTx = session.BeginTransaction(definition.TransactionIsolationLevel); + IDbTransaction adoTx = GetIDbTransaction(hibernateTx); + + // Add the Hibernate transaction to the session holder. + txObject.SessionHolder.Transaction = hibernateTx; + + // Register transaction timeout. + int timeout = DetermineTimeout(definition); + if (timeout != DefaultTransactionDefinition.TIMEOUT_DEFAULT) + { + txObject.SessionHolder.TimeoutInSeconds = timeout; + } + + // Register the Hibernate Session's ADO.NET Connection/TX pair for the DbProvider, if set. + if (DbProvider != null) + { + //investigate passing null for tx. + ConnectionHolder conHolder = new ConnectionHolder(con, adoTx); + if (timeout != DefaultTransactionDefinition.TIMEOUT_DEFAULT) + { + conHolder.TimeoutInMillis = definition.TransactionTimeout; + } + if (log.IsDebugEnabled) + { + log.Debug("Exposing Hibernate transaction as ADO transaction [" + con + "]"); + } + TransactionSynchronizationManager.BindResource(DbProvider, conHolder); + txObject.ConnectionHolder = conHolder; + } + + // Bind the session holder to the thread. + if (txObject.NewSessionHolder) + { + TransactionSynchronizationManager.BindResource(SessionFactory, txObject.SessionHolder); + } + + } + catch (Exception ex) + { + SessionFactoryUtils.CloseSession(session); + throw new CannotCreateTransactionException("Could not open Hibernate Session for transaction", ex); + } + + + } + + + + + /// + /// Suspend the resources of the current transaction. + /// + /// + /// Transaction object returned by + /// . + /// + /// + /// An object that holds suspended resources (will be kept unexamined for passing it into + /// .) + /// + /// + /// Transaction synchronization will already have been suspended. + /// + /// + /// If suspending is not supported by the transaction manager implementation. + /// + /// + /// in case of system errors. + /// + protected override object DoSuspend(object transaction) + { + HibernateTransactionObject txObject = (HibernateTransactionObject)transaction; + txObject.SetSessionHolder(null, false); + SessionHolder sessionHolder = + (SessionHolder)TransactionSynchronizationManager.UnbindResource(SessionFactory); + ConnectionHolder connectionHolder = null; + if (DbProvider != null) + { + connectionHolder = (ConnectionHolder)TransactionSynchronizationManager.UnbindResource(DbProvider); + } + return new SuspendedResourcesHolder(sessionHolder, connectionHolder); + + } + + /// + /// Resume the resources of the current transaction. + /// + /// + /// Transaction object returned by + /// . + /// + /// + /// The object that holds suspended resources as returned by + /// . + /// + /// + /// Transaction synchronization will be resumed afterwards. + /// + /// + /// If suspending is not supported by the transaction manager implementation. + /// + /// + /// In the case of system errors. + /// + protected override void DoResume(object transaction, object suspendedResources) + { + SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder)suspendedResources; + if (TransactionSynchronizationManager.HasResource(SessionFactory)) + { + // From non-transactional code running in active transaction synchronization + // -> can be safely removed, will be closed on transaction completion. + TransactionSynchronizationManager.UnbindResource(SessionFactory); + } + TransactionSynchronizationManager.BindResource(SessionFactory, resourcesHolder.SessionHolder); + if (DbProvider != null) + { + TransactionSynchronizationManager.BindResource(DbProvider, resourcesHolder.ConnectionHolder); + } + } + + /// + /// Perform an actual commit on the given transaction. + /// + /// The status representation of the transaction. + /// + ///

+ /// An implementation does not need to check the rollback-only flag. + ///

+ ///
+ /// + /// In the case of system errors. + /// + protected override void DoCommit(DefaultTransactionStatus status) + { + HibernateTransactionObject txObject = (HibernateTransactionObject)status.Transaction; + if (status.Debug) + { + log.Debug("Committing Hibernate transaction on Session [" + + txObject.SessionHolder.Session + "]"); + } + try + { + txObject.SessionHolder.Transaction.Commit(); + } + // Note, unfortunate collision of namespaces/classname for NHibernate.TransactionException + // and Spring.Data.NHibernate requires this wierd construct. + catch (Exception ex) + { + Type nhibTxExceptiontype = TypeResolutionUtils.ResolveType("NHibernate.TransactionException, NHibernate"); + if (ex.GetType().Equals(nhibTxExceptiontype)) + { + // assumably from commit call to the underlying ADO.NET connection + throw new TransactionSystemException("Could not commit Hibernate transaction", ex); + } + HibernateException hibEx = ex as HibernateException; + if (hibEx != null) + { + // assumably failed to flush changes to database + throw ConvertHibernateAccessException(hibEx); + } + throw; + } + } + + /// + /// Perform an actual rollback on the given transaction. + /// + /// The status representation of the transaction. + /// + /// An implementation does not need to check the new transaction flag. + /// + /// + /// In the case of system errors. + /// + protected override void DoRollback(DefaultTransactionStatus status) + { + HibernateTransactionObject txObject = (HibernateTransactionObject)status.Transaction; + + if (status.Debug) + { + log.Debug("Rolling back Hibernate transaction on Session [" + + txObject.SessionHolder.Session + "]"); + } + try + { + IDbTransaction adoTx = GetIDbTransaction(txObject.SessionHolder.Transaction); + + if (adoTx != null && adoTx.Connection != null) + { + txObject.SessionHolder.Transaction.Rollback(); + } + } + catch (HibernateTransactionException ex) + { + throw new TransactionSystemException("Could not roll back Hibernate transaction", ex); + } + catch (HibernateException ex) + { + // Shouldn't really happen, as a rollback doesn't cause a flush. + throw ConvertHibernateAccessException(ex); + } + finally + { + if (!txObject.NewSessionHolder) + { + // Clear all pending inserts/updates/deletes in the Session. + // Necessary for pre-bound Sessions, to avoid inconsistent state. + txObject.SessionHolder.Session.Clear(); + } + } + + + + } + + + /// + /// Set the given transaction rollback-only. Only called on rollback + /// if the current transaction takes part in an existing one. + /// + /// The status representation of the transaction. + /// + /// In the case of system errors. + /// + protected override void DoSetRollbackOnly(DefaultTransactionStatus status) + { + HibernateTransactionObject txObject = (HibernateTransactionObject)status.Transaction; + if (status.Debug) + { + log.Debug("Setting Hibernate transaction on Session [" + + txObject.SessionHolder.Session + "] rollback-only"); + } + txObject.SetRollbackOnly(); + } + + + /// + /// Gets the ADO.NET IDbTransaction object from the NHibernate ITransaction object. + /// + /// The hibernate transaction. + /// The ADO.NET transaction. Null if could not get the transaction. Warning + /// messages will be logged in that case. + protected virtual IDbTransaction GetIDbTransaction(ITransaction hibernateTx) + { + AdoTransaction hibernateAdoTx = hibernateTx as AdoTransaction; + + IDbTransaction adoTransaction = null; + if (hibernateAdoTx != null) + { + try + { + FieldInfo fi = hibernateAdoTx.GetType().GetField("trans", BindingFlags.Instance | BindingFlags.NonPublic); + adoTransaction = fi.GetValue(hibernateAdoTx) as IDbTransaction; + } + catch (Exception e) + { + log.Warn("Could not extract IDbTransaction from Hibernate AdoTransaction using field name trans.", e); + } + } + else + { + log.Warn("Hibernate ITransaction not of expected type AdoTransaction. Could not extract IDbTransaction from Hibernate AdoTransaction."); + } + return adoTransaction; + } + + /// + /// Convert the given HibernateException to an appropriate exception from + /// the Spring.Dao hierarchy. Can be overridden in subclasses. + /// + /// The HibernateException that occured. + /// The corresponding DataAccessException instance + protected virtual DataAccessException ConvertHibernateAccessException(HibernateException ex) + { + if (AdoExceptionTranslator != null && ex is ADOException) + { + return ConvertAdoAccessException((ADOException)ex, AdoExceptionTranslator); + } + else if (ex is ADOException) + { + return ConvertAdoAccessException((ADOException)ex, DefaultAdoExceptionTranslator); + } + return SessionFactoryUtils.ConvertHibernateAccessException(ex); + } + + /// + /// Convert the given ADOException to an appropriate exception from the + /// the Spring.Dao hierarchy. Can be overridden in subclasses. + /// + /// The ADOException that occured, wrapping the underlying + /// ADO.NET thrown exception. + /// The translator to convert hibernate ADOExceptions. + /// + /// The corresponding DataAccessException instance + /// + protected virtual DataAccessException ConvertAdoAccessException(ADOException ex, IAdoExceptionTranslator translator) + { + return translator.Translate("Hibernate flusing: " + ex.Message, null, ex.InnerException); + } + + /// + /// Cleanup resources after transaction completion. + /// + /// Transaction object returned by + /// . + /// + /// + /// This implemenation unbinds the SessionFactory and + /// DbProvider from thread local storage and closes the + /// ISession. + /// + ///

+ /// Called after + /// and + /// + /// execution on any outcome. + ///

+ ///

+ /// Should not throw any exceptions but just issue warnings on errors. + ///

+ ///
+ protected override void DoCleanupAfterCompletion(object transaction) + { + HibernateTransactionObject txObject = (HibernateTransactionObject)transaction; + + // Remove the session holder from the thread. + if (txObject.NewSessionHolder) + { + TransactionSynchronizationManager.UnbindResource(SessionFactory); + } + // Remove the ADO.NET connection holder from the thread, if exposed. + if (DbProvider != null) + { + TransactionSynchronizationManager.UnbindResource(DbProvider); + } + + ISession session = txObject.SessionHolder.Session; + if (txObject.NewSessionHolder) + { + if (log.IsDebugEnabled) + { + log.Debug("Closing Hibernate Session [" + session + "] after transaction"); + } + SessionFactoryUtils.CloseSessionOrRegisterDeferredClose(session, SessionFactory); + } + else + { + if (log.IsDebugEnabled) + { + log.Debug("Not closing pre-bound Hibernate Session [" + session + "] after transaction"); + } + if (txObject.SessionHolder.AssignedPreviousFlushMode) + { + session.FlushMode = txObject.SessionHolder.PreviousFlushMode; + } + } + txObject.SessionHolder.Clear(); + + + } + + private class HibernateTransactionObject : AdoTransactionObjectSupport + { + + private SessionHolder sessionHolder; + + private bool newSessionHolder; + + + public void SetSessionHolder(SessionHolder sessionHolder, bool newSessionHolder) + { + this.sessionHolder = sessionHolder; + this.newSessionHolder = newSessionHolder; + } + + + public SessionHolder SessionHolder + { + get + { + return sessionHolder; + } + } + + public bool NewSessionHolder + { + get + { + return newSessionHolder; + } + } + + public bool HasTransaction() + { + return (this.sessionHolder != null && this.sessionHolder.Transaction != null); + } + + public void SetRollbackOnly() + { + SessionHolder.RollbackOnly = true; + if (ConnectionHolder != null) + { + ConnectionHolder.RollbackOnly = true; + } + } + + /// + /// Return whether the transaction is internally marked as rollback-only. + /// + /// + /// True of the transaction is marked as rollback-only. + public override bool RollbackOnly + { + get + { + return SessionHolder.RollbackOnly || + (ConnectionHolder != null && ConnectionHolder.RollbackOnly); + } + } + } + + private class SuspendedResourcesHolder + { + + private readonly SessionHolder sessionHolder; + + private readonly ConnectionHolder connectionHolder; + + public SuspendedResourcesHolder(SessionHolder sessionHolder, ConnectionHolder conHolder) + { + this.sessionHolder = sessionHolder; + this.connectionHolder = conHolder; + } + + public SessionHolder SessionHolder + { + get + { + return sessionHolder; + } + + } + + public ConnectionHolder ConnectionHolder + { + get + { + return connectionHolder; + } + + } + } + + /// + /// Invoked by an + /// after it has injected all of an object's dependencies. + /// + /// + ///

+ /// This method allows the object instance to perform the kind of + /// initialization only possible when all of it's dependencies have + /// been injected (set), and to throw an appropriate exception in the + /// event of misconfiguration. + ///

+ ///

+ /// Please do consult the class level documentation for the + /// interface for a + /// description of exactly when this method is invoked. In + /// particular, it is worth noting that the + /// + /// and + /// callbacks will have been invoked prior to this method being + /// called. + ///

+ ///
+ /// + /// In the event of misconfiguration (such as the failure to set a + /// required property) or if initialization fails. + /// + public void AfterPropertiesSet() + { + if (SessionFactory == null) + { + throw new ArgumentException("sessionFactory is required"); + } + if (this.entityInterceptor is string && this.objectFactory == null) + { + throw new ArgumentException("objectFactory is required for entityInterceptorBeanName"); + } + + // Try to derive a DbProvider given the SessionFactory. + if (this.autodetectDbProvider && DbProvider == null) + { + IDbProvider sfDbProvider = SessionFactoryUtils.GetDbProvider(SessionFactory); + if (sfDbProvider != null) + { + // Use the SessionFactory's DataSource for exposing transactions to ADO.NET code. + if (log.IsInfoEnabled) + { + log.Info("Derived DbProvider [" + sfDbProvider.DbMetadata.ProductName + + "] of Hibernate SessionFactory for HibernateTransactionManager"); + } + DbProvider = sfDbProvider; + } + else + { + log.Info("Could not auto detect DbProvider from SessionFactory configuration"); + } + + } + } + } +} diff --git a/src/Spring/Spring.Data.NHibernate12/Data/NHibernate/SessionFactoryUtils.cs b/src/Spring/Spring.Data.NHibernate12/Data/NHibernate/SessionFactoryUtils.cs index 53a009c4..723c246a 100644 --- a/src/Spring/Spring.Data.NHibernate12/Data/NHibernate/SessionFactoryUtils.cs +++ b/src/Spring/Spring.Data.NHibernate12/Data/NHibernate/SessionFactoryUtils.cs @@ -260,7 +260,7 @@ namespace Spring.Data.NHibernate // Spring transaction management is active -> // register pre-bound Session with it for transactional flushing. session = sessionHolder.ValidatedSession; - if (!sessionHolder.SynchronizedWithTransaction) + if (session != null && !sessionHolder.SynchronizedWithTransaction) { log.Debug("Registering Spring transaction synchronization for existing Hibernate Session"); TransactionSynchronizationManager.RegisterSynchronization( diff --git a/src/Spring/Spring.Data.NHibernate21/Data/NHibernate/HibernateTxScopeTransactionManager.cs b/src/Spring/Spring.Data.NHibernate21/Data/NHibernate/HibernateTxScopeTransactionManager.cs index a5dd49e8..5e1c5c05 100644 --- a/src/Spring/Spring.Data.NHibernate21/Data/NHibernate/HibernateTxScopeTransactionManager.cs +++ b/src/Spring/Spring.Data.NHibernate21/Data/NHibernate/HibernateTxScopeTransactionManager.cs @@ -716,6 +716,20 @@ namespace Spring.Data.NHibernate protected override void DoRollback(DefaultTransactionStatus status) { HibernateTransactionObject txObject = (HibernateTransactionObject)status.Transaction; + + if (!txObject.NewSessionHolder) + { + // Clear all pending inserts/updates/deletes in the Session. + // Necessary for pre-bound Sessions, to avoid inconsistent state. + txObject.SessionHolder.Session.Clear(); + } + + DoTxScopeRollback(status); + return; + + +/* HibernateTransactionObject txObject = (HibernateTransactionObject)status.Transaction; + if (status.Debug) { log.Debug("Rolling back Hibernate transaction on Session [" + @@ -723,6 +737,11 @@ namespace Spring.Data.NHibernate } try { + if (txObject.SessionHolder.Session != null && txObject.SessionHolder.Transaction != null && !txObject.SessionHolder.Transaction.IsActive) + { + return; + } + IDbTransaction adoTx = GetIDbTransaction(txObject.SessionHolder.Transaction); if (adoTx != null && adoTx.Connection != null) @@ -737,7 +756,7 @@ namespace Spring.Data.NHibernate txObject.SessionHolder.Session + "] was null"); } } - + } catch (HibernateTransactionException ex) { @@ -757,7 +776,7 @@ namespace Spring.Data.NHibernate txObject.SessionHolder.Session.Clear(); } DoTxScopeRollback(status); - } + }*/ } /// diff --git a/src/Spring/Spring.Data/Data/Core/TxScopeTransactionManager.cs b/src/Spring/Spring.Data/Data/Core/TxScopeTransactionManager.cs index 2664b2e3..0d8af06d 100644 --- a/src/Spring/Spring.Data/Data/Core/TxScopeTransactionManager.cs +++ b/src/Spring/Spring.Data/Data/Core/TxScopeTransactionManager.cs @@ -1,288 +1,288 @@ -#if NET_2_0 - -#region License - -/* - * Copyright 2007 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. - */ - -#endregion - -using System; -using System.Transactions; -using Spring.Data.Support; -using Spring.Objects.Factory; -using Spring.Transaction; -using Spring.Transaction.Support; - -namespace Spring.Data.Core -{ - /// - /// TransactionManager that uses TransactionScope provided by System.Transactions. - /// - /// Mark Pollack (.NET) - public class TxScopeTransactionManager : AbstractPlatformTransactionManager, IInitializingObject - { - private readonly ITransactionScopeAdapter txAdapter; - - /// - /// Initializes a new instance of the class. - /// - public TxScopeTransactionManager() - { - // noop - } - - /// - /// Initializes a new instance of the class. - /// - /// This is indented only for unit testing purposes and should not be - /// called by production application code. - /// The tx adapter. - public TxScopeTransactionManager(ITransactionScopeAdapter txAdapter) - { - this.txAdapter = txAdapter; - } - - #region IInitializingObject Members - - /// - /// No-op initialization - /// - public void AfterPropertiesSet() - { - // placeholder for more advanced configurations. - } - - #endregion - - protected override object DoGetTransaction() - { - PromotableTxScopeTransactionObject txObject = new PromotableTxScopeTransactionObject(); - - if (txAdapter != null) - { - txObject.TxScopeAdapter = txAdapter; - } - return txObject; - } - - protected override bool IsExistingTransaction(object transaction) - { - PromotableTxScopeTransactionObject txObject = - (PromotableTxScopeTransactionObject)transaction; - return txObject.TxScopeAdapter.IsExistingTransaction; - } - - protected override void DoBegin(object transaction, Spring.Transaction.ITransactionDefinition definition) - { - PromotableTxScopeTransactionObject txObject = - (PromotableTxScopeTransactionObject)transaction; - try - { - DoTxScopeBegin(txObject, definition); - } - catch (Exception e) - { - throw new CannotCreateTransactionException("Transaction Scope failure on begin", e); - } - } - - protected override object DoSuspend(object transaction) - { - // Passing the current TxScopeAdapter as the 'suspended resource', even though it is not used just to avoid passing null - // TxScopeTransactionManager is not binding any resources to the local thread, instead delegating to - // System.Transactions to handle thread local resources. - PromotableTxScopeTransactionObject txMgrStateObject = (PromotableTxScopeTransactionObject) transaction; - return txMgrStateObject.TxScopeAdapter; - } - - protected override void DoResume(object transaction, object suspendedResources) - { - } - - protected override void DoCommit(DefaultTransactionStatus status) - { - PromotableTxScopeTransactionObject txObject = - (PromotableTxScopeTransactionObject)status.Transaction; - try - { - txObject.TxScopeAdapter.Complete(); - txObject.TxScopeAdapter.Dispose(); - } - catch (TransactionAbortedException ex) - { - throw new UnexpectedRollbackException("Transaction unexpectedly rolled back (maybe due to a timeout)", ex); - } - catch (TransactionInDoubtException ex) - { - throw new HeuristicCompletionException(TransactionOutcomeState.Unknown, ex); - } - catch (Exception ex) - { - throw new TransactionSystemException("Failure on Transaction Scope Commit", ex); - } - } - - protected override void DoRollback(DefaultTransactionStatus status) - { - PromotableTxScopeTransactionObject txObject = - (PromotableTxScopeTransactionObject)status.Transaction; - - try - { - - txObject.TxScopeAdapter.Dispose(); - } - catch (Exception e) - { - throw new Spring.Transaction.TransactionSystemException("Failure on Transaction Scope rollback.", e); - } - } - - - protected override void DoSetRollbackOnly(DefaultTransactionStatus status) - { - if (status.Debug) - { - log.Debug("Setting transaction rollback-only"); - } - try - { - System.Transactions.Transaction.Current.Rollback(); - } catch (Exception ex) - { - throw new TransactionSystemException("Failure on System.Transactions.Transaction.Current.Rollback", ex); - } - } - - protected override bool ShouldCommitOnGlobalRollbackOnly - { - get { return true; } - } - - private void DoTxScopeBegin(PromotableTxScopeTransactionObject txObject, - Spring.Transaction.ITransactionDefinition definition) - { - - TransactionScopeOption txScopeOption = CreateTransactionScopeOptions(definition); - TransactionOptions txOptions = CreateTransactionOptions(definition); - txObject.TxScopeAdapter.CreateTransactionScope(txScopeOption, txOptions, definition.EnterpriseServicesInteropOption); - - } - - private static TransactionOptions CreateTransactionOptions(ITransactionDefinition definition) - { - TransactionOptions txOptions = new TransactionOptions(); - switch (definition.TransactionIsolationLevel ) - { - case System.Data.IsolationLevel.Chaos: - txOptions.IsolationLevel = IsolationLevel.Chaos; - break; - case System.Data.IsolationLevel.ReadCommitted: - txOptions.IsolationLevel = IsolationLevel.ReadCommitted; - break; - case System.Data.IsolationLevel.ReadUncommitted: - txOptions.IsolationLevel = IsolationLevel.ReadUncommitted; - break; - case System.Data.IsolationLevel.RepeatableRead: - txOptions.IsolationLevel = IsolationLevel.RepeatableRead; - break; - case System.Data.IsolationLevel.Serializable: - txOptions.IsolationLevel = IsolationLevel.Serializable; - break; - case System.Data.IsolationLevel.Snapshot: - txOptions.IsolationLevel = IsolationLevel.Snapshot; - break; - case System.Data.IsolationLevel.Unspecified: - txOptions.IsolationLevel = IsolationLevel.Unspecified; - break; - } - - if (definition.TransactionTimeout != DefaultTransactionDefinition.TIMEOUT_DEFAULT) - { - txOptions.Timeout = new TimeSpan(0, 0, definition.TransactionTimeout); - } - return txOptions; - } - - private static TransactionScopeOption CreateTransactionScopeOptions(ITransactionDefinition definition) - { - TransactionScopeOption txScopeOption; - if (definition.PropagationBehavior == TransactionPropagation.Required) - { - txScopeOption = TransactionScopeOption.Required; - } - else if (definition.PropagationBehavior == TransactionPropagation.RequiresNew) - { - txScopeOption = TransactionScopeOption.RequiresNew; - } - else if (definition.PropagationBehavior == TransactionPropagation.NotSupported) +#if NET_2_0 + +#region License + +/* + * Copyright 2007 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. + */ + +#endregion + +using System; +using System.Transactions; +using Spring.Data.Support; +using Spring.Objects.Factory; +using Spring.Transaction; +using Spring.Transaction.Support; + +namespace Spring.Data.Core +{ + /// + /// TransactionManager that uses TransactionScope provided by System.Transactions. + /// + /// Mark Pollack (.NET) + public class TxScopeTransactionManager : AbstractPlatformTransactionManager, IInitializingObject + { + private readonly ITransactionScopeAdapter txAdapter; + + /// + /// Initializes a new instance of the class. + /// + public TxScopeTransactionManager() + { + // noop + } + + /// + /// Initializes a new instance of the class. + /// + /// This is indented only for unit testing purposes and should not be + /// called by production application code. + /// The tx adapter. + public TxScopeTransactionManager(ITransactionScopeAdapter txAdapter) + { + this.txAdapter = txAdapter; + } + + #region IInitializingObject Members + + /// + /// No-op initialization + /// + public void AfterPropertiesSet() + { + // placeholder for more advanced configurations. + } + + #endregion + + protected override object DoGetTransaction() + { + PromotableTxScopeTransactionObject txObject = new PromotableTxScopeTransactionObject(); + + if (txAdapter != null) + { + txObject.TxScopeAdapter = txAdapter; + } + return txObject; + } + + protected override bool IsExistingTransaction(object transaction) + { + PromotableTxScopeTransactionObject txObject = + (PromotableTxScopeTransactionObject)transaction; + return txObject.TxScopeAdapter.IsExistingTransaction; + } + + protected override void DoBegin(object transaction, Spring.Transaction.ITransactionDefinition definition) + { + PromotableTxScopeTransactionObject txObject = + (PromotableTxScopeTransactionObject)transaction; + try + { + DoTxScopeBegin(txObject, definition); + } + catch (Exception e) + { + throw new CannotCreateTransactionException("Transaction Scope failure on begin", e); + } + } + + protected override object DoSuspend(object transaction) + { + // Passing the current TxScopeAdapter as the 'suspended resource', even though it is not used just to avoid passing null + // TxScopeTransactionManager is not binding any resources to the local thread, instead delegating to + // System.Transactions to handle thread local resources. + PromotableTxScopeTransactionObject txMgrStateObject = (PromotableTxScopeTransactionObject) transaction; + return txMgrStateObject.TxScopeAdapter; + } + + protected override void DoResume(object transaction, object suspendedResources) + { + } + + protected override void DoCommit(DefaultTransactionStatus status) + { + PromotableTxScopeTransactionObject txObject = + (PromotableTxScopeTransactionObject)status.Transaction; + try + { + txObject.TxScopeAdapter.Complete(); + txObject.TxScopeAdapter.Dispose(); + } + catch (TransactionAbortedException ex) + { + throw new UnexpectedRollbackException("Transaction unexpectedly rolled back (maybe due to a timeout)", ex); + } + catch (TransactionInDoubtException ex) + { + throw new HeuristicCompletionException(TransactionOutcomeState.Unknown, ex); + } + catch (Exception ex) + { + throw new TransactionSystemException("Failure on Transaction Scope Commit", ex); + } + } + + protected override void DoRollback(DefaultTransactionStatus status) + { + PromotableTxScopeTransactionObject txObject = + (PromotableTxScopeTransactionObject)status.Transaction; + + try + { + + txObject.TxScopeAdapter.Dispose(); + } + catch (Exception e) + { + throw new Spring.Transaction.TransactionSystemException("Failure on Transaction Scope rollback.", e); + } + } + + + protected override void DoSetRollbackOnly(DefaultTransactionStatus status) + { + if (status.Debug) + { + log.Debug("Setting transaction rollback-only"); + } + try + { + System.Transactions.Transaction.Current.Rollback(); + } catch (Exception ex) + { + throw new TransactionSystemException("Failure on System.Transactions.Transaction.Current.Rollback", ex); + } + } + + protected override bool ShouldCommitOnGlobalRollbackOnly + { + get { return true; } + } + + private void DoTxScopeBegin(PromotableTxScopeTransactionObject txObject, + Spring.Transaction.ITransactionDefinition definition) + { + + TransactionScopeOption txScopeOption = CreateTransactionScopeOptions(definition); + TransactionOptions txOptions = CreateTransactionOptions(definition); + txObject.TxScopeAdapter.CreateTransactionScope(txScopeOption, txOptions, definition.EnterpriseServicesInteropOption); + + } + + private static TransactionOptions CreateTransactionOptions(ITransactionDefinition definition) + { + TransactionOptions txOptions = new TransactionOptions(); + switch (definition.TransactionIsolationLevel ) + { + case System.Data.IsolationLevel.Chaos: + txOptions.IsolationLevel = IsolationLevel.Chaos; + break; + case System.Data.IsolationLevel.ReadCommitted: + txOptions.IsolationLevel = IsolationLevel.ReadCommitted; + break; + case System.Data.IsolationLevel.ReadUncommitted: + txOptions.IsolationLevel = IsolationLevel.ReadUncommitted; + break; + case System.Data.IsolationLevel.RepeatableRead: + txOptions.IsolationLevel = IsolationLevel.RepeatableRead; + break; + case System.Data.IsolationLevel.Serializable: + txOptions.IsolationLevel = IsolationLevel.Serializable; + break; + case System.Data.IsolationLevel.Snapshot: + txOptions.IsolationLevel = IsolationLevel.Snapshot; + break; + case System.Data.IsolationLevel.Unspecified: + txOptions.IsolationLevel = IsolationLevel.Unspecified; + break; + } + + if (definition.TransactionTimeout != DefaultTransactionDefinition.TIMEOUT_DEFAULT) + { + txOptions.Timeout = new TimeSpan(0, 0, definition.TransactionTimeout); + } + return txOptions; + } + + private static TransactionScopeOption CreateTransactionScopeOptions(ITransactionDefinition definition) + { + TransactionScopeOption txScopeOption; + if (definition.PropagationBehavior == TransactionPropagation.Required) + { + txScopeOption = TransactionScopeOption.Required; + } + else if (definition.PropagationBehavior == TransactionPropagation.RequiresNew) + { + txScopeOption = TransactionScopeOption.RequiresNew; + } + else if (definition.PropagationBehavior == TransactionPropagation.NotSupported) { txScopeOption = TransactionScopeOption.Suppress; - } - else - { - throw new Spring.Transaction.TransactionSystemException("Transaction Propagation Behavior" + - definition.PropagationBehavior + - " not supported by TransactionScope. Use Required or RequiredNew"); - } - return txScopeOption; - } - - /// - /// The transaction resource object that encapsulates the state and functionality - /// contained in TransactionScope and Transaction.Current via the ITransactionScopeAdapter - /// property. - /// - public class PromotableTxScopeTransactionObject : ISmartTransactionObject - { - private ITransactionScopeAdapter txScopeAdapter; - - /// - /// Initializes a new instance of the class. - /// Will create an instance of . - /// - public PromotableTxScopeTransactionObject() - { - txScopeAdapter = new DefaultTransactionScopeAdapter(); - } - - /// - /// Gets or sets the transaction scope adapter. - /// - /// The transaction scope adapter. - public ITransactionScopeAdapter TxScopeAdapter - { - get { return txScopeAdapter; } - set { txScopeAdapter = value; } - } - - - /// - /// Return whether the transaction is internally marked as rollback-only. - /// - /// - /// True of the transaction is marked as rollback-only. - public bool RollbackOnly - { - get { - return txScopeAdapter.RollbackOnly; - } - } - } - } -} + } + else + { + throw new Spring.Transaction.TransactionSystemException("Transaction Propagation Behavior" + + definition.PropagationBehavior + + " not supported by TransactionScope. Use Required or RequiredNew"); + } + return txScopeOption; + } + + /// + /// The transaction resource object that encapsulates the state and functionality + /// contained in TransactionScope and Transaction.Current via the ITransactionScopeAdapter + /// property. + /// + public class PromotableTxScopeTransactionObject : ISmartTransactionObject + { + private ITransactionScopeAdapter txScopeAdapter; + + /// + /// Initializes a new instance of the class. + /// Will create an instance of . + /// + public PromotableTxScopeTransactionObject() + { + txScopeAdapter = new DefaultTransactionScopeAdapter(); + } + + /// + /// Gets or sets the transaction scope adapter. + /// + /// The transaction scope adapter. + public ITransactionScopeAdapter TxScopeAdapter + { + get { return txScopeAdapter; } + set { txScopeAdapter = value; } + } + + + /// + /// Return whether the transaction is internally marked as rollback-only. + /// + /// + /// True of the transaction is marked as rollback-only. + public bool RollbackOnly + { + get { + return txScopeAdapter.RollbackOnly; + } + } + } + } +} #endif \ No newline at end of file diff --git a/src/Spring/Spring.Data/Transaction/Support/AbstractPlatformTransactionManager.cs b/src/Spring/Spring.Data/Transaction/Support/AbstractPlatformTransactionManager.cs index b10021e4..e132100c 100644 --- a/src/Spring/Spring.Data/Transaction/Support/AbstractPlatformTransactionManager.cs +++ b/src/Spring/Spring.Data/Transaction/Support/AbstractPlatformTransactionManager.cs @@ -1,422 +1,423 @@ -#region License - -/* - * Copyright 2002-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. - */ - -#endregion - -using System; -using System.Collections; -using System.Data; -using Common.Logging; - -namespace Spring.Transaction.Support -{ - /// - /// Abstract base class that allows for easy implementation of concrete platform transaction managers. - /// - /// - ///

Provides the following workflow handling: - ///

    - ///
  • Determines if there is an existing transaction
  • - ///
  • Applies the appropriate propagation behavior
  • - ///
  • Suspends and resumes transactions if necessary
  • - ///
  • Checks the rollback-only flag on commit
  • - ///
  • Applies the appropriate modification on rollback (actual rollback or setting rollback-only)
  • - ///
  • Triggers registered synchronization callbacks (if transaction synchronization is active)
  • - ///
- ///

- ///

- /// Transaction synchronization is a generic mechanism for registering - /// callbacks that get invoked at transaction completion time. The same mechanism - /// can also be used for custom synchronization efforts. - ///

- ///

- /// The state of this class is serializable. It's up to subclasses if - /// they wish to make their state to be serializable. - /// They should implement if they need - /// to restore any transient state. - ///

- ///
- /// Juergen Hoeller - /// Mark Pollack (.NET) - /// Griffin Caprio (.NET) - [Serializable] - public abstract class AbstractPlatformTransactionManager : IPlatformTransactionManager - { - #region Private SuspendedResourcesHolder Helper class - - private class SuspendedResourcesHolder - { - private IList _suspendedSynchronizations; - private object _suspendedResources; - private string _name; - private bool _readOnly; - private IsolationLevel _isolationLevel; - private bool _wasActive; - - - public SuspendedResourcesHolder(object suspendedResources) - { - _suspendedResources = suspendedResources; - } - - public SuspendedResourcesHolder(IList suspendedSynchronizations, object suspendedResources, - string name, bool readOnly, IsolationLevel isolationLevel, bool wasActive) - { - _suspendedSynchronizations = suspendedSynchronizations; - _suspendedResources = suspendedResources; - _name = name; - _readOnly = readOnly; - _isolationLevel = isolationLevel; - _wasActive = wasActive; - } - - public IList SuspendedSynchronizations - { - get { return _suspendedSynchronizations; } - } - - public object SuspendedResources - { - get { return _suspendedResources; } - } - - - public string Name - { - get { return _name; } - } - - public bool ReadOnly - { - get { return _readOnly; } - } - - public IsolationLevel IsolationLevel - { - get { return _isolationLevel; } - } - - public bool WasActive - { - get { return _wasActive; } - } - } - - #endregion - - #region Private Variables - - private TransactionSynchronizationState _transactionSyncState = TransactionSynchronizationState.Always; - private bool _nestedTransactionsAllowed; - private bool _rollbackOnCommitFailure; - private bool _failEarlyOnGlobalRollbackOnly; - private int _defaultTimeout = DefaultTransactionDefinition.TIMEOUT_DEFAULT; - - #region Logging Definition - - [NonSerialized()] protected readonly ILog log; - - #endregion - - protected AbstractPlatformTransactionManager() +#region License + +/* + * Copyright 2002-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. + */ + +#endregion + +using System; +using System.Collections; +using System.Data; +using Common.Logging; + +namespace Spring.Transaction.Support +{ + /// + /// Abstract base class that allows for easy implementation of concrete platform transaction managers. + /// + /// + ///

Provides the following workflow handling: + ///

    + ///
  • Determines if there is an existing transaction
  • + ///
  • Applies the appropriate propagation behavior
  • + ///
  • Suspends and resumes transactions if necessary
  • + ///
  • Checks the rollback-only flag on commit
  • + ///
  • Applies the appropriate modification on rollback (actual rollback or setting rollback-only)
  • + ///
  • Triggers registered synchronization callbacks (if transaction synchronization is active)
  • + ///
+ ///

+ ///

+ /// Transaction synchronization is a generic mechanism for registering + /// callbacks that get invoked at transaction completion time. The same mechanism + /// can also be used for custom synchronization efforts. + ///

+ ///

+ /// The state of this class is serializable. It's up to subclasses if + /// they wish to make their state to be serializable. + /// They should implement if they need + /// to restore any transient state. + ///

+ ///
+ /// Juergen Hoeller + /// Mark Pollack (.NET) + /// Griffin Caprio (.NET) + [Serializable] + public abstract class AbstractPlatformTransactionManager : IPlatformTransactionManager + { + #region Private SuspendedResourcesHolder Helper class + + private class SuspendedResourcesHolder + { + private IList _suspendedSynchronizations; + private object _suspendedResources; + private string _name; + private bool _readOnly; + private IsolationLevel _isolationLevel; + private bool _wasActive; + + + public SuspendedResourcesHolder(object suspendedResources) + { + _suspendedResources = suspendedResources; + } + + public SuspendedResourcesHolder(IList suspendedSynchronizations, object suspendedResources, + string name, bool readOnly, IsolationLevel isolationLevel, bool wasActive) + { + _suspendedSynchronizations = suspendedSynchronizations; + _suspendedResources = suspendedResources; + _name = name; + _readOnly = readOnly; + _isolationLevel = isolationLevel; + _wasActive = wasActive; + } + + public IList SuspendedSynchronizations + { + get { return _suspendedSynchronizations; } + } + + public object SuspendedResources + { + get { return _suspendedResources; } + } + + + public string Name + { + get { return _name; } + } + + public bool ReadOnly + { + get { return _readOnly; } + } + + public IsolationLevel IsolationLevel + { + get { return _isolationLevel; } + } + + public bool WasActive + { + get { return _wasActive; } + } + } + + #endregion + + #region Private Variables + + private TransactionSynchronizationState _transactionSyncState = TransactionSynchronizationState.Never; + private bool _nestedTransactionsAllowed; + private bool _rollbackOnCommitFailure; + private bool _failEarlyOnGlobalRollbackOnly; + private int _defaultTimeout = DefaultTransactionDefinition.TIMEOUT_DEFAULT; + + #region Logging Definition + + [NonSerialized()] + protected readonly ILog log; + + #endregion + + protected AbstractPlatformTransactionManager() { log = LogManager.GetLogger(this.GetType()); } - #endregion - - #region Public Properties - - /// - /// Sets and gets when this transaction manager should activate the thread-bound - /// transaction synchronization support. Default is "always". - /// - /// - ///

- /// Note that transaction synchronization isn't supported for - /// multiple concurrent transactions by different transaction managers. - /// Only one transaction manager is allowed to activate it at any time. - ///

- /// - ///
- public TransactionSynchronizationState TransactionSynchronization - { - set { _transactionSyncState = value; } - get { return _transactionSyncState; } - } - - /// - /// Sets and gets whether nested transactions are allowed. Default is false. - /// - /// - ///

- /// Typically initialized with an appropriate default by the - /// concrete transaction manager subclass. - ///

- ///
- public bool NestedTransactionsAllowed - { - get { return _nestedTransactionsAllowed; } - set { _nestedTransactionsAllowed = value; } - } - - /// - /// Sets and gets a flag that determines whether or not the - /// - /// method must be invoked if a call to the - /// - /// method fails. Default is false. - /// - /// - /// Typically not necessary and thus to be avoided as it can override the - /// commit exception with a subsequent rollback exception. - /// - public bool RollbackOnCommitFailure - { - get { return _rollbackOnCommitFailure; } - set { _rollbackOnCommitFailure = value; } - } - - - /// - /// Gets or sets a value indicating whether to fail early in case of the transaction being - /// globally marked as rollback-only. - /// - /// - /// Default is "false", only causing an UnexpectedRollbackException at the - /// outermost transaction boundary. Switch this flag on to cause an - /// UnexpectedRollbackException as early as the global rollback-only marker - /// has been first detected, even from within an inner transaction boundary. - /// - /// - /// true if fail early on global rollback; otherwise, false. - /// - public bool FailEarlyOnGlobalRollbackOnly - { - get { return _failEarlyOnGlobalRollbackOnly; } - set { _failEarlyOnGlobalRollbackOnly = value; } - } - - /// - /// Gets or sets the default timeout that this transaction manager should apply if there - /// is no timeout specified at the transaction level, in seconds. - /// - /// Returns DefaultTransactionDefinition.TIMEOUT_DEFAULT to indicate the - /// underlying transaction infrastructure's default timeout. - /// The default timeout. - public int DefaultTimeout - { - get - { - return _defaultTimeout; - } - set - { - if (_defaultTimeout < DefaultTransactionDefinition.TIMEOUT_DEFAULT) - { - throw new InvalidTimeoutException("Invalid default timeout", _defaultTimeout); - } - _defaultTimeout = value; - } - } - - #endregion - - #region Protected Methods - - /// - /// Return the current transaction object. - /// - /// The current transaction object. - /// - /// If transaction support is not available. - /// - /// - /// In the case of lookup or system errors. - /// - protected abstract object DoGetTransaction(); - - /// - /// Check if the given transaction object indicates an existing transaction - /// (that is, a transaction which has already started). - /// - /// - /// The result will be evaluated according to the specified propagation - /// behavior for the new transaction. An existing transaction might get - /// suspended (in case of PROPAGATION_REQUIRES_NEW), or the new transaction - /// might participate in the existing one (in case of PROPAGATION_REQUIRED). - /// Default implementation returns false, assuming that detection of or - /// participating in existing transactions is generally not supported. - /// Subclasses are of course encouraged to provide such support. - /// - /// Transaction object returned by - /// . - /// - /// True if there is an existing transaction. - /// - /// In the case of system errors. - /// - protected virtual bool IsExistingTransaction(object transaction) - { - return false; - } - - /// - /// Begin a new transaction with the given transaction definition. - /// - /// - /// Does not have to care about applying the propagation behavior, - /// as this has already been handled by this abstract manager. - /// - /// - /// Transaction object returned by - /// . - /// - /// - /// instance, describing - /// propagation behavior, isolation level, timeout etc. - /// - /// - /// In the case of creation or system errors. - /// - protected abstract void DoBegin(object transaction, ITransactionDefinition definition); - - /// - /// Suspend the resources of the current transaction. - /// - /// - /// Transaction synchronization will already have been suspended. - /// - /// Default implementation throws a TransactionSuspensionNotSupportedException, - /// assuming that transaction suspension is generally not supported. - /// - /// - /// - /// Transaction object returned by - /// . - /// - /// - /// An object that holds suspended resources (will be kept unexamined for passing it into - /// .) - /// - /// - /// If suspending is not supported by the transaction manager implementation. - /// - /// - /// in case of system errors. - /// - protected virtual object DoSuspend(object transaction) - { - throw new TransactionSuspensionNotSupportedException( - "Transaction manager [" + GetType().Name + "] does not support transaction suspension"); - } - - /// - /// Resume the resources of the current transaction. - /// - /// Transaction synchronization will be resumed afterwards. - /// - /// Default implementation throws a TransactionSuspensionNotSupportedException, - /// assuming that transaction suspension is generally not supported. - /// - /// - /// - /// Transaction object returned by - /// . - /// - /// - /// The object that holds suspended resources as returned by - /// . - /// - /// - /// If suspending is not supported by the transaction manager implementation. - /// - /// - /// In the case of system errors. - /// - protected virtual void DoResume(object transaction, object suspendedResources) - { - throw new TransactionSuspensionNotSupportedException( - "Transaction manager [" + GetType().Name + "] does not support transaction suspension"); - } - - /// - /// Perform an actual commit on the given transaction. - /// - /// The status representation of the transaction. - /// - ///

- /// An implementation does not need to check the rollback-only flag. - ///

- ///
- /// - /// In the case of system errors. - /// - protected abstract void DoCommit(DefaultTransactionStatus status); - - /// - /// Perform an actual rollback on the given transaction. - /// - /// The status representation of the transaction. - /// - /// An implementation does not need to check the new transaction flag. - /// - /// - /// In the case of system errors. - /// - protected abstract void DoRollback(DefaultTransactionStatus status); - - /// - /// Set the given transaction rollback-only. Only called on rollback - /// if the current transaction takes part in an existing one. - /// - /// Default implementation throws an IllegalTransactionStateException, - /// assuming that participating in existing transactions is generally not - /// supported. Subclasses are of course encouraged to provide such support. - /// - /// The status representation of the transaction. - /// - /// In the case of system errors. - /// - protected virtual void DoSetRollbackOnly(DefaultTransactionStatus status) - { - throw new IllegalTransactionStateException( - "Participating in existing transactions is not supported - when 'IsExistingTransaction' " + - "returns true, appropriate 'DoSetRollbackOnly' behavior must be provided"); - } - - /// - /// Return whether to use a savepoint for a nested transaction. Default is true, - /// which causes delegation to - /// for holding a savepoint. - /// - /// - /// - ///

- /// Subclasses can override this to return false, causing a further - /// invocation of - /// - /// despite an already existing transaction. - ///

- ///
- protected virtual bool UseSavepointForNestedTransaction() - { - return true; + #endregion + + #region Public Properties + + /// + /// Sets and gets when this transaction manager should activate the thread-bound + /// transaction synchronization support. Default is "always". + /// + /// + ///

+ /// Note that transaction synchronization isn't supported for + /// multiple concurrent transactions by different transaction managers. + /// Only one transaction manager is allowed to activate it at any time. + ///

+ /// + ///
+ public TransactionSynchronizationState TransactionSynchronization + { + set { _transactionSyncState = value; } + get { return _transactionSyncState; } + } + + /// + /// Sets and gets whether nested transactions are allowed. Default is false. + /// + /// + ///

+ /// Typically initialized with an appropriate default by the + /// concrete transaction manager subclass. + ///

+ ///
+ public bool NestedTransactionsAllowed + { + get { return _nestedTransactionsAllowed; } + set { _nestedTransactionsAllowed = value; } + } + + /// + /// Sets and gets a flag that determines whether or not the + /// + /// method must be invoked if a call to the + /// + /// method fails. Default is false. + /// + /// + /// Typically not necessary and thus to be avoided as it can override the + /// commit exception with a subsequent rollback exception. + /// + public bool RollbackOnCommitFailure + { + get { return _rollbackOnCommitFailure; } + set { _rollbackOnCommitFailure = value; } + } + + + /// + /// Gets or sets a value indicating whether to fail early in case of the transaction being + /// globally marked as rollback-only. + /// + /// + /// Default is "false", only causing an UnexpectedRollbackException at the + /// outermost transaction boundary. Switch this flag on to cause an + /// UnexpectedRollbackException as early as the global rollback-only marker + /// has been first detected, even from within an inner transaction boundary. + /// + /// + /// true if fail early on global rollback; otherwise, false. + /// + public bool FailEarlyOnGlobalRollbackOnly + { + get { return _failEarlyOnGlobalRollbackOnly; } + set { _failEarlyOnGlobalRollbackOnly = value; } + } + + /// + /// Gets or sets the default timeout that this transaction manager should apply if there + /// is no timeout specified at the transaction level, in seconds. + /// + /// Returns DefaultTransactionDefinition.TIMEOUT_DEFAULT to indicate the + /// underlying transaction infrastructure's default timeout. + /// The default timeout. + public int DefaultTimeout + { + get + { + return _defaultTimeout; + } + set + { + if (_defaultTimeout < DefaultTransactionDefinition.TIMEOUT_DEFAULT) + { + throw new InvalidTimeoutException("Invalid default timeout", _defaultTimeout); + } + _defaultTimeout = value; + } + } + + #endregion + + #region Protected Methods + + /// + /// Return the current transaction object. + /// + /// The current transaction object. + /// + /// If transaction support is not available. + /// + /// + /// In the case of lookup or system errors. + /// + protected abstract object DoGetTransaction(); + + /// + /// Check if the given transaction object indicates an existing transaction + /// (that is, a transaction which has already started). + /// + /// + /// The result will be evaluated according to the specified propagation + /// behavior for the new transaction. An existing transaction might get + /// suspended (in case of PROPAGATION_REQUIRES_NEW), or the new transaction + /// might participate in the existing one (in case of PROPAGATION_REQUIRED). + /// Default implementation returns false, assuming that detection of or + /// participating in existing transactions is generally not supported. + /// Subclasses are of course encouraged to provide such support. + /// + /// Transaction object returned by + /// . + /// + /// True if there is an existing transaction. + /// + /// In the case of system errors. + /// + protected virtual bool IsExistingTransaction(object transaction) + { + return false; + } + + /// + /// Begin a new transaction with the given transaction definition. + /// + /// + /// Does not have to care about applying the propagation behavior, + /// as this has already been handled by this abstract manager. + /// + /// + /// Transaction object returned by + /// . + /// + /// + /// instance, describing + /// propagation behavior, isolation level, timeout etc. + /// + /// + /// In the case of creation or system errors. + /// + protected abstract void DoBegin(object transaction, ITransactionDefinition definition); + + /// + /// Suspend the resources of the current transaction. + /// + /// + /// Transaction synchronization will already have been suspended. + /// + /// Default implementation throws a TransactionSuspensionNotSupportedException, + /// assuming that transaction suspension is generally not supported. + /// + /// + /// + /// Transaction object returned by + /// . + /// + /// + /// An object that holds suspended resources (will be kept unexamined for passing it into + /// .) + /// + /// + /// If suspending is not supported by the transaction manager implementation. + /// + /// + /// in case of system errors. + /// + protected virtual object DoSuspend(object transaction) + { + throw new TransactionSuspensionNotSupportedException( + "Transaction manager [" + GetType().Name + "] does not support transaction suspension"); + } + + /// + /// Resume the resources of the current transaction. + /// + /// Transaction synchronization will be resumed afterwards. + /// + /// Default implementation throws a TransactionSuspensionNotSupportedException, + /// assuming that transaction suspension is generally not supported. + /// + /// + /// + /// Transaction object returned by + /// . + /// + /// + /// The object that holds suspended resources as returned by + /// . + /// + /// + /// If suspending is not supported by the transaction manager implementation. + /// + /// + /// In the case of system errors. + /// + protected virtual void DoResume(object transaction, object suspendedResources) + { + throw new TransactionSuspensionNotSupportedException( + "Transaction manager [" + GetType().Name + "] does not support transaction suspension"); + } + + /// + /// Perform an actual commit on the given transaction. + /// + /// The status representation of the transaction. + /// + ///

+ /// An implementation does not need to check the rollback-only flag. + ///

+ ///
+ /// + /// In the case of system errors. + /// + protected abstract void DoCommit(DefaultTransactionStatus status); + + /// + /// Perform an actual rollback on the given transaction. + /// + /// The status representation of the transaction. + /// + /// An implementation does not need to check the new transaction flag. + /// + /// + /// In the case of system errors. + /// + protected abstract void DoRollback(DefaultTransactionStatus status); + + /// + /// Set the given transaction rollback-only. Only called on rollback + /// if the current transaction takes part in an existing one. + /// + /// Default implementation throws an IllegalTransactionStateException, + /// assuming that participating in existing transactions is generally not + /// supported. Subclasses are of course encouraged to provide such support. + /// + /// The status representation of the transaction. + /// + /// In the case of system errors. + /// + protected virtual void DoSetRollbackOnly(DefaultTransactionStatus status) + { + throw new IllegalTransactionStateException( + "Participating in existing transactions is not supported - when 'IsExistingTransaction' " + + "returns true, appropriate 'DoSetRollbackOnly' behavior must be provided"); + } + + /// + /// Return whether to use a savepoint for a nested transaction. Default is true, + /// which causes delegation to + /// for holding a savepoint. + /// + /// + /// + ///

+ /// Subclasses can override this to return false, causing a further + /// invocation of + /// + /// despite an already existing transaction. + ///

+ ///
+ protected virtual bool UseSavepointForNestedTransaction() + { + return true; } /// @@ -441,774 +442,836 @@ namespace Spring.Transaction.Support /// /// protected virtual void RegisterAfterCompletionWithExistingTransaction(Object transaction, IList synchronizations) - { + { - log.Debug("Cannot register Spring after-completion synchronization with existing transaction - " + - "processing Spring after-completion callbacks immediately, with outcome status 'unknown'"); - InvokeAfterCompletion(synchronizations, TransactionSynchronizationStatus.Unknown); - } - - /// - /// Cleanup resources after transaction completion. - /// - /// - /// Transaction object returned by - /// . - /// - /// - /// - /// Called after - /// and - /// - /// execution on any outcome. - /// - /// - /// Should not throw any exceptions but just issue warnings on errors. - /// - /// - /// Default implementation does nothing. - /// - /// - protected virtual void DoCleanupAfterCompletion(object transaction) - { - } - - #endregion - - #region IPlatformTransactionManager Members - - /// - /// Return a currently active transaction or create a new one. - /// - /// - ///

- /// This implementation handles propagation behavior. - ///

- ///

- /// Delegates to - /// , - /// , - /// and - /// . - ///

- ///

- /// Note that parameters like isolation level or timeout will only be applied - /// to new transactions, and thus be ignored when participating in active ones. - /// Furthermore, they aren't supported by every transaction manager: - /// a proper implementation should throw an exception when custom values - /// that it doesn't support are specified. - ///

- ///
- /// - /// instance (can be null for - /// defaults), describing propagation behavior, isolation level, timeout etc. - /// - /// - /// In case of lookup, creation, or system errors. - /// - /// - /// representing the new or current transaction. - /// - public ITransactionStatus GetTransaction(ITransactionDefinition definition) - { - object transaction = DoGetTransaction(); - bool debugEnabled = log.IsDebugEnabled; - bool newSynchronization; - - if (debugEnabled) - { - log.Debug("Using transaction object [" + transaction + "]"); - } - - if (definition == null) - { - definition = new DefaultTransactionDefinition(); - } - if (IsExistingTransaction(transaction)) - { - // Existing transaction found -> check propagation behavior to find out how to behave. - return HandleExistingTransaction(definition, transaction, debugEnabled); - } - - // Check definition settings for new transaction. - if (definition.TransactionTimeout < DefaultTransactionDefinition.TIMEOUT_DEFAULT) - { - throw new InvalidTimeoutException("Invalid transaction timeout", definition.TransactionTimeout); - } - - // No existing transaction found -> check propagation behavior to find out how to proceed. - if (definition.PropagationBehavior == TransactionPropagation.Mandatory) - { - throw new IllegalTransactionStateException( - "Transaction propagation 'mandatory' but no existing transaction found"); - } - else if (definition.PropagationBehavior == TransactionPropagation.Required || - definition.PropagationBehavior == TransactionPropagation.RequiresNew || - definition.PropagationBehavior == TransactionPropagation.Nested) - { - object suspendedResources = Suspend(null); - if (debugEnabled) - { - log.Debug("Creating new transaction with name [" + definition.Name + "]:" + definition); - } - try - { - DoBegin(transaction, definition); - } catch (TransactionException) - { - Resume(null, suspendedResources); - throw; - } - newSynchronization = (_transactionSyncState != TransactionSynchronizationState.Never); - return NewTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, - suspendedResources); - } - else - { - // Create "empty" transaction: no actual transaction, but potentially synchronization. - newSynchronization = (_transactionSyncState == TransactionSynchronizationState.Always); - return NewTransactionStatus(definition, null, false, newSynchronization, debugEnabled, null); - - } - - } - - private ITransactionStatus HandleExistingTransaction(ITransactionDefinition definition, object transaction, bool debugEnabled) - { - //bool newSynchronization; - if (definition.PropagationBehavior == TransactionPropagation.Never) - { - throw new IllegalTransactionStateException( - "Transaction propagation 'never' but existing transaction found."); - } - if (definition.PropagationBehavior == TransactionPropagation.NotSupported) - { - if (debugEnabled) - { - log.Debug("Suspending current transaction"); - } - object suspendedResources = Suspend(transaction); - bool newSynchronization = (_transactionSyncState == TransactionSynchronizationState.Always); - return - NewTransactionStatus(definition, null, false, newSynchronization, debugEnabled, - suspendedResources); - } - - if (definition.PropagationBehavior == TransactionPropagation.RequiresNew) - { - if (debugEnabled) - { - log.Debug("Suspending current transaction, creating new transaction with name [" + - definition.Name + "]:" + definition); - } - object suspendedResources = Suspend(transaction); - try - { - DoBegin(transaction, definition); - } - catch (TransactionException beginEx) - { - try - { - Resume(transaction, suspendedResources); - } - catch (TransactionException resumeEx) - { - log.Error( - "Inner transaction begin exception overridden by outer transaction resume exception"); - log.Error("Begin Transaction Exception", beginEx); - log.Error("Resume Transaction Exception", resumeEx); - throw; - } - throw; - } - bool newSynchronization = (_transactionSyncState != TransactionSynchronizationState.Never); - return - NewTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); - } - if (definition.PropagationBehavior == TransactionPropagation.Nested) - { - if (!NestedTransactionsAllowed) - { - throw new NestedTransactionNotSupportedException( - "Transaction manager does not allow nested transactions by default - " + - "specify 'NestedTransactionsAllowed' property with value 'true'"); - } - if (debugEnabled) - { - log.Debug("Creating nested transaction with name [" + definition.Name + "]:" + definition); - } - - if (UseSavepointForNestedTransaction()) - { - DefaultTransactionStatus status = - NewTransactionStatus(definition, transaction, false, false, debugEnabled, null); - status.CreateAndHoldSavepoint(DateTime.Now.ToLongTimeString()); - return status; - } - else - { - DoBegin(transaction, definition); - bool newSynchronization = (_transactionSyncState != TransactionSynchronizationState.Never); - return NewTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null); - - } - } - // Assumably PROPAGATION_SUPPORTS. - if (debugEnabled) { - log.Debug("Participating in existing transaction"); - } - bool newSynch = (_transactionSyncState != TransactionSynchronizationState.Never); - return NewTransactionStatus(definition, transaction, false, newSynch, debugEnabled, null); - - } - - /// - /// This implementation of commit handles participating in existing transactions - /// and programmatic rollback requests. - /// - /// - /// - /// - /// ITransactionStatus object returned by the - /// () method. - /// - /// - /// In case of commit or system errors. - /// - public void Commit(ITransactionStatus transactionStatus) - { - if (transactionStatus.Completed) - { - throw new IllegalTransactionStateException( - "Transaction is already completed - do not call commit or rollback more than once per transaction"); - } - - DefaultTransactionStatus defaultStatus = (DefaultTransactionStatus) transactionStatus; - if (defaultStatus.LocalRollbackOnly) - { - if (defaultStatus.Debug) - { - log.Debug("Transaction code has requested rollback"); - } - ProcessRollback(defaultStatus); - return; - } - if ( !ShouldCommitOnGlobalRollbackOnly && defaultStatus.GlobalRollbackOnly) - { - if (defaultStatus.Debug) - { - log.Debug("Global transaction is marked as rollback-only but transactional code requested commit"); - } - ProcessRollback(defaultStatus); - // Throw UnexpectedRollbackException only at outermost transaction boundary - // or if explicitly asked to. - if (defaultStatus.IsNewTransaction || FailEarlyOnGlobalRollbackOnly) - { - throw new UnexpectedRollbackException( - "Transaction rolled back because it has been marked as rollback-only"); - } - return; - } - ProcessCommit(defaultStatus); - - } - - protected virtual bool ShouldCommitOnGlobalRollbackOnly - { - get { return false; } - } - - private void ProcessCommit(DefaultTransactionStatus status) - { - try - { - bool beforeCompletionInvoked = false; - try - { - TriggerBeforeCommit(status); - TriggerBeforeCompletion(status); - beforeCompletionInvoked = true; - bool globalRollbackOnly = false; - if (status.IsNewTransaction || FailEarlyOnGlobalRollbackOnly) - { - globalRollbackOnly = status.GlobalRollbackOnly; - } - if (status.HasSavepoint) - { - status.ReleaseHeldSavepoint(); - } - else if (status.IsNewTransaction) - { - DoCommit(status); - } - // Throw UnexpectedRollbackException if we have a global rollback-only - // marker but still didn't get a corresponding exception from commit. - if (globalRollbackOnly) - { - throw new UnexpectedRollbackException( - "Transaction silently rolled back because it has been marked as rollback-only"); - } - } - catch (UnexpectedRollbackException) - { - TriggerAfterCompletion(status, TransactionSynchronizationStatus.Rolledback); - throw; - } - catch (TransactionException ex) - { - if (RollbackOnCommitFailure) - { - DoRollbackOnCommitException(status, ex); - } - else - { - TriggerAfterCompletion(status, TransactionSynchronizationStatus.Unknown); - } - throw; - } - catch (Exception ex) - { - if (!beforeCompletionInvoked) - { - TriggerBeforeCompletion(status); - } - DoRollbackOnCommitException(status, ex); - throw; - } - // Trigger AfterCommit callbacks, with an exception thrown there - // propagated to callers but the transaction still considered as commited. - try - { - TriggerAfterCommit(status); - } - finally - { - TriggerAfterCompletion(status, TransactionSynchronizationStatus.Committed); - } - } - finally - { - CleanupAfterCompletion(status); - } - } - - private void TriggerAfterCommit(DefaultTransactionStatus status) - { - if (status.NewSynchronization) - { - if (status.Debug) - { - log.Debug("Trigger AfterCommit Synchronization"); - } - IList synchronizations = TransactionSynchronizationManager.Synchronizations; - foreach (ITransactionSynchronization currentTxnSynchronization in synchronizations) - { - try - { - currentTxnSynchronization.AfterCommit(); - } - catch (Exception e) - { - log.Error("TransactionSynchronization.AfterCommit thew exception", e); - } - } - } - } - - /// - /// Roll back the given transaction, with regard to its status. - /// - /// - ///

- /// This implementation handles participating in existing transactions. - ///

- ///

- /// Delegates to - /// , - /// and - /// . - ///

- ///

- /// If the transaction wasn't a new one, just set it rollback-only - /// to take part in the surrounding transaction properly. - ///

- ///
- /// - /// ITransactionStatusObject returned by the - /// () method. - /// - /// - /// In case of system errors. - /// - public void Rollback(ITransactionStatus transactionStatus) - { - if (transactionStatus.Completed) - { - throw new IllegalTransactionStateException( - "Transaction is already completed - do not call commit or rollback more than once per transaction"); - } - DefaultTransactionStatus defaultStatus = (DefaultTransactionStatus) transactionStatus; - ProcessRollback(defaultStatus); - } - - private void ProcessRollback(DefaultTransactionStatus status) - { - try - { - try - { - TriggerBeforeCompletion(status); - if (status.HasSavepoint) - { - if (status.Debug) - { - log.Debug("Rolling back transaction to savepoint."); - } - status.RollbackToHeldSavepoint(); - } - else if (status.IsNewTransaction) - { - if (status.Debug) - { - log.Debug("Initiating transaction rollback"); - } - DoRollback(status); - } - else if (status.HasTransaction()) - { - if (status.LocalRollbackOnly) - { - if(status.Debug) - { - log.Debug("Participating transaction failed - marking existing transaction as rollback-only"); - } - } - DoSetRollbackOnly(status); - } - else - { - log.Debug("Should roll back transaction but cannot - no transaction available."); - } - } - catch (Exception) - { - TriggerAfterCompletion(status, TransactionSynchronizationStatus.Unknown); - throw; - } - TriggerAfterCompletion(status, TransactionSynchronizationStatus.Rolledback); - } - finally - { - CleanupAfterCompletion(status); - } - } - - #endregion - - #region Protected Method - - private DefaultTransactionStatus NewTransactionStatus(ITransactionDefinition definition, - object transaction, bool newTransaction, - bool newSynchronization, bool debug, - object suspendedResources) - { - bool actualNewSynchronization = newSynchronization && - !TransactionSynchronizationManager.SynchronizationActive; - if (actualNewSynchronization) - { - TransactionSynchronizationManager.ActualTransactionActive = (transaction != null); - TransactionSynchronizationManager.CurrentTransactionIsolationLevel = - definition.TransactionIsolationLevel; - TransactionSynchronizationManager.CurrentTransactionReadOnly = definition.ReadOnly; - TransactionSynchronizationManager.CurrentTransactionName = definition.Name; - TransactionSynchronizationManager.InitSynchronization(); - } - return - new DefaultTransactionStatus(transaction, newTransaction, actualNewSynchronization, definition.ReadOnly, debug, - suspendedResources); - } - - /// - /// Determines the timeout to use for the given definition. Will fall back to this manager's default - /// timeout if the transaction definition doesn't specify a non-default value. - /// - /// The transaction definition. - /// the actual timeout to use. - protected int DetermineTimeout(ITransactionDefinition definition) - { - if (definition.TransactionTimeout != DefaultTransactionDefinition.TIMEOUT_DEFAULT) - { - return definition.TransactionTimeout; - } - return _defaultTimeout; - } - - #endregion - - #region Private Methods - - - - - - /// - /// Suspend the given transaction. Suspends transaction synchronization first, - /// then delegates to the doSuspend template method. - /// - /// the current transaction object - /// an object that holds suspended resources - private object Suspend(object transaction) - { - if (TransactionSynchronizationManager.SynchronizationActive) - { - IList suspendedSynchronizations = DoSuspendSynchronization(); - - try - { - object suspendedResources = null; - if (transaction != null) - { - suspendedResources = DoSuspend(transaction); - } - - string name = TransactionSynchronizationManager.CurrentTransactionName; - TransactionSynchronizationManager.CurrentTransactionName = null; - bool readOnly = TransactionSynchronizationManager.CurrentTransactionReadOnly; - TransactionSynchronizationManager.CurrentTransactionReadOnly = false; - IsolationLevel isolationLevel = TransactionSynchronizationManager.CurrentTransactionIsolationLevel; - TransactionSynchronizationManager.CurrentTransactionIsolationLevel = IsolationLevel.Unspecified; - bool wasActive = TransactionSynchronizationManager.ActualTransactionActive; - TransactionSynchronizationManager.ActualTransactionActive = false; - - - return new SuspendedResourcesHolder(suspendedSynchronizations, suspendedResources, - name, readOnly, isolationLevel, wasActive); - - } catch (TransactionException) - { - // DoSuspend failed - original transaction is still active - DoResumeSynchronization(suspendedSynchronizations); - throw; - } - } - else if (transaction != null) - { - // Transaction active but no synchronization active. - object suspendedResources = DoSuspend(transaction); - return new SuspendedResourcesHolder(suspendedResources); - } - else - { - // Neither transaction nor synchronization active. - return null; - } - - } - - private IList DoSuspendSynchronization() - { - IList suspendedSynchronizations = TransactionSynchronizationManager.Synchronizations; - foreach (ITransactionSynchronization currentTxnSynchronization in suspendedSynchronizations) - { - currentTxnSynchronization.Suspend(); - } - TransactionSynchronizationManager.ClearSynchronization(); - return suspendedSynchronizations; - } - - private void DoResumeSynchronization(IList suspendedSynchronizations) - { - TransactionSynchronizationManager.InitSynchronization(); - foreach (ITransactionSynchronization currentTxnSynchronization in suspendedSynchronizations) - { - currentTxnSynchronization.Resume(); - TransactionSynchronizationManager.RegisterSynchronization(currentTxnSynchronization); - } - } - - /// - /// Resume the given transaction. Delegates to the doResume template method - /// first, then resuming transaction synchronization. - /// - /// the current transaction object - /// the object that holds suspended resources, as returned by suspend - private void Resume(object transaction, object suspendedResources) - { - SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources; - if (resourcesHolder != null) - { - object suspendedResourcesObject = resourcesHolder.SuspendedResources; - if (suspendedResourcesObject != null) - { - DoResume(transaction, suspendedResourcesObject); - } - IList suspendedSynchronizations = resourcesHolder.SuspendedSynchronizations; - if (suspendedSynchronizations != null) - { - TransactionSynchronizationManager.ActualTransactionActive = resourcesHolder.WasActive; - TransactionSynchronizationManager.CurrentTransactionIsolationLevel = resourcesHolder.IsolationLevel; - TransactionSynchronizationManager.CurrentTransactionReadOnly = resourcesHolder.ReadOnly; - TransactionSynchronizationManager.CurrentTransactionName = resourcesHolder.Name; - DoResumeSynchronization(suspendedSynchronizations); - } - - } - } - - - - /// - /// Invoke doRollback, handling rollback exceptions properly. - /// - /// object representing the transaction - /// the thrown application exception or error - /// - /// in case of a rollback error - /// - private void DoRollbackOnCommitException(DefaultTransactionStatus status, Exception exception) - { - try - { - if (status.IsNewTransaction) - { - if (status.Debug) - { - log.Debug("Initiating transaction rollback on commit exception."); - } - DoRollback(status); - } - else if (status.HasTransaction()) - { - if (status.Debug) - { - log.Debug("Marking existing transaction as rollback-only after commit exception", exception); - } - DoSetRollbackOnly(status); - } - } - catch (Exception) - { - //TODO investigate rollback behavior... - log.Error("Commit exception overridden by rollback exception", exception); - TriggerAfterCompletion(status, TransactionSynchronizationStatus.Unknown); - throw; - } - TriggerAfterCompletion(status, TransactionSynchronizationStatus.Rolledback); - } - - /// - /// Trigger beforeCommit callback. - /// - /// object representing the transaction - private void TriggerBeforeCommit(DefaultTransactionStatus status) - { - if (status.NewSynchronization) - { - IList synchronizations = TransactionSynchronizationManager.Synchronizations; - foreach (ITransactionSynchronization currentTxnSynchronization in synchronizations) - { - currentTxnSynchronization.BeforeCommit(status.ReadOnly); - } - } - } - - /// - /// Trigger beforeCompletion callback. - /// - /// object representing the transaction - private void TriggerBeforeCompletion(DefaultTransactionStatus status) - { - if (status.NewSynchronization) - { - if (status.Debug) - { - log.Debug("Trigger BeforeCompletion Synchronization"); - } - IList synchronizations = TransactionSynchronizationManager.Synchronizations; - foreach (ITransactionSynchronization synchronization in synchronizations) - { - try - { - synchronization.BeforeCompletion(); - } - catch (Exception e) - { - log.Error("TransactionSynchronization.BeforeCompletion threw exception", e); - } - } - } - } - - /// - /// Trigger afterCompletion callback, handling exceptions properly. - /// - /// object representing the transaction - /// - /// Completion status according to - /// - private void TriggerAfterCompletion(DefaultTransactionStatus status, TransactionSynchronizationStatus completionStatus) - { - if (status.NewSynchronization) - { - IList synchronizations = TransactionSynchronizationManager.Synchronizations; - if (!status.HasTransaction() || status.IsNewTransaction) - { - if (status.Debug) - { - log.Debug("Triggering afterCompletion synchronization"); - } - InvokeAfterCompletion(synchronizations, completionStatus); - } - else - { - //TODO investigate parallel of JTA/System.Txs + log.Debug("Cannot register Spring after-completion synchronization with existing transaction - " + + "processing Spring after-completion callbacks immediately, with outcome status 'unknown'"); + InvokeAfterCompletion(synchronizations, TransactionSynchronizationStatus.Unknown); + } + + /// + /// Cleanup resources after transaction completion. + /// + /// + /// Transaction object returned by + /// . + /// + /// + /// + /// Called after + /// and + /// + /// execution on any outcome. + /// + /// + /// Should not throw any exceptions but just issue warnings on errors. + /// + /// + /// Default implementation does nothing. + /// + /// + protected virtual void DoCleanupAfterCompletion(object transaction) + { + } + + #endregion + + #region IPlatformTransactionManager Members + + /// + /// Return a currently active transaction or create a new one. + /// + /// + ///

+ /// This implementation handles propagation behavior. + ///

+ ///

+ /// Delegates to + /// , + /// , + /// and + /// . + ///

+ ///

+ /// Note that parameters like isolation level or timeout will only be applied + /// to new transactions, and thus be ignored when participating in active ones. + /// Furthermore, they aren't supported by every transaction manager: + /// a proper implementation should throw an exception when custom values + /// that it doesn't support are specified. + ///

+ ///
+ /// + /// instance (can be null for + /// defaults), describing propagation behavior, isolation level, timeout etc. + /// + /// + /// In case of lookup, creation, or system errors. + /// + /// + /// representing the new or current transaction. + /// + public ITransactionStatus GetTransaction(ITransactionDefinition definition) + { + object transaction = DoGetTransaction(); + bool debugEnabled = log.IsDebugEnabled; + + if (debugEnabled) + { + log.Debug("Using transaction object [" + transaction + "]"); + } + + if (definition == null) + { + definition = new DefaultTransactionDefinition(); + } + if (IsExistingTransaction(transaction)) + { + // Existing transaction found -> check propagation behavior to find out how to behave. + return HandleExistingTransaction(definition, transaction, debugEnabled); + } + + // Check definition settings for new transaction. + if (definition.TransactionTimeout < DefaultTransactionDefinition.TIMEOUT_DEFAULT) + { + throw new InvalidTimeoutException("Invalid transaction timeout", definition.TransactionTimeout); + } + + // No existing transaction found -> check propagation behavior to find out how to proceed. + if (definition.PropagationBehavior == TransactionPropagation.Mandatory) + { + throw new IllegalTransactionStateException( + "Transaction propagation 'mandatory' but no existing transaction found"); + } + else if (definition.PropagationBehavior == TransactionPropagation.Required || + definition.PropagationBehavior == TransactionPropagation.RequiresNew || + definition.PropagationBehavior == TransactionPropagation.Nested) + { + object suspendedResources = Suspend(null); + if (debugEnabled) + { + log.Debug("Creating new transaction with name [" + definition.Name + "]:" + definition); + } + try + { + bool newSynchronization = (_transactionSyncState != TransactionSynchronizationState.Never); + DefaultTransactionStatus status = NewTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, + suspendedResources); + DoBegin(transaction, definition); + PrepareSynchronization(status, definition); + return status; + } + catch (TransactionException) + { + Resume(null, suspendedResources); + throw; + } + } + else + { + // Create "empty" transaction: no actual transaction, but potentially synchronization. + bool newSynchronization = (_transactionSyncState == TransactionSynchronizationState.Always); + return PrepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null); + + } + + } + + protected DefaultTransactionStatus PrepareTransactionStatus( + ITransactionDefinition definition, Object transaction, bool newTransaction, + bool newSynchronization, bool debug, Object suspendedResources) + { + DefaultTransactionStatus status = NewTransactionStatus( + definition, transaction, newTransaction, newSynchronization, debug, suspendedResources); + PrepareSynchronization(status, definition); + return status; + } + + + protected void PrepareSynchronization(DefaultTransactionStatus status, ITransactionDefinition definition) + { + if (status.NewSynchronization) + { + TransactionSynchronizationManager.ActualTransactionActive = status.HasTransaction(); + TransactionSynchronizationManager.CurrentTransactionIsolationLevel = definition.TransactionIsolationLevel != System.Data.IsolationLevel.Unspecified ? definition.TransactionIsolationLevel : IsolationLevel.Unspecified; + TransactionSynchronizationManager.CurrentTransactionReadOnly = definition.ReadOnly; + TransactionSynchronizationManager.CurrentTransactionName = definition.Name; + TransactionSynchronizationManager.InitSynchronization(); + } + } + + private ITransactionStatus HandleExistingTransaction(ITransactionDefinition definition, object transaction, bool debugEnabled) + { + //bool newSynchronization; + if (definition.PropagationBehavior == TransactionPropagation.Never) + { + throw new IllegalTransactionStateException( + "Transaction propagation 'never' but existing transaction found."); + } + if (definition.PropagationBehavior == TransactionPropagation.NotSupported) + { + if (debugEnabled) + { + log.Debug("Suspending current transaction"); + } + object suspendedResources = Suspend(transaction); + bool newSynchronization = (_transactionSyncState == TransactionSynchronizationState.Always); + return + PrepareTransactionStatus(definition, null, false, newSynchronization, debugEnabled, + suspendedResources); + } + + if (definition.PropagationBehavior == TransactionPropagation.RequiresNew) + { + if (debugEnabled) + { + log.Debug("Suspending current transaction, creating new transaction with name [" + + definition.Name + "]:" + definition); + } + object suspendedResources = Suspend(transaction); + try + { + bool newSynchronization = (_transactionSyncState != TransactionSynchronizationState.Never); + DefaultTransactionStatus status = NewTransactionStatus( + definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); + PrepareSynchronization(status, definition); + DoBegin(transaction, definition); + return status; + } + catch (TransactionException beginEx) + { + try + { + Resume(transaction, suspendedResources); + //TODO: java code rethrows the ex here...should we do so as well? + //throw; + } + catch (TransactionException resumeEx) + { + log.Error( + "Inner transaction begin exception overridden by outer transaction resume exception"); + log.Error("Begin Transaction Exception", beginEx); + log.Error("Resume Transaction Exception", resumeEx); + throw; + } + throw; + } + } + if (definition.PropagationBehavior == TransactionPropagation.Nested) + { + if (!NestedTransactionsAllowed) + { + throw new NestedTransactionNotSupportedException( + "Transaction manager does not allow nested transactions by default - " + + "specify 'NestedTransactionsAllowed' property with value 'true'"); + } + if (debugEnabled) + { + log.Debug("Creating nested transaction with name [" + definition.Name + "]:" + definition); + } + + if (UseSavepointForNestedTransaction()) + { + DefaultTransactionStatus status = + PrepareTransactionStatus(definition, transaction, false, false, debugEnabled, null); + status.CreateAndHoldSavepoint(DateTime.Now.ToLongTimeString()); + return status; + } + else + { + bool newSynchronization = (_transactionSyncState != TransactionSynchronizationState.Never); + DefaultTransactionStatus status = NewTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null); + PrepareSynchronization(status, definition); + DoBegin(transaction, definition); + return status; + + } + } + // Assumably PROPAGATION_SUPPORTS. + if (debugEnabled) + { + log.Debug("Participating in existing transaction"); + } + + //TODO: this block related to un-ported java feature permitting setting the ValidateExistingTransaction flag + // default is FALSE anyway so skipping this validation block should have no effect on code excecution path + /*if (isValidateExistingTransaction()) + { + if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) + { + Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel(); + if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) + { + Constants isoConstants = DefaultTransactionDefinition.constants; + throw new IllegalTransactionStateException("Participating transaction with definition [" + + definition + "] specifies isolation level which is incompatible with existing transaction: " + + (currentIsolationLevel != null ? + isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) : + "(unknown)")); + } + }*/ + + if (!definition.ReadOnly) + { + if (TransactionSynchronizationManager.CurrentTransactionReadOnly) + { + throw new IllegalTransactionStateException("Participating transaction with definition [" + + definition + "] is not marked as read-only but existing transaction is"); + } + } + + bool newSynch = (_transactionSyncState != TransactionSynchronizationState.Never); + return PrepareTransactionStatus(definition, transaction, false, newSynch, debugEnabled, null); + + } + + /// + /// This implementation of commit handles participating in existing transactions + /// and programmatic rollback requests. + /// + /// + /// + /// + /// ITransactionStatus object returned by the + /// () method. + /// + /// + /// In case of commit or system errors. + /// + public void Commit(ITransactionStatus transactionStatus) + { + if (transactionStatus.Completed) + { + throw new IllegalTransactionStateException( + "Transaction is already completed - do not call commit or rollback more than once per transaction"); + } + + DefaultTransactionStatus defaultStatus = (DefaultTransactionStatus)transactionStatus; + if (defaultStatus.LocalRollbackOnly) + { + if (defaultStatus.Debug) + { + log.Debug("Transaction code has requested rollback"); + } + ProcessRollback(defaultStatus); + return; + } + if (!ShouldCommitOnGlobalRollbackOnly && defaultStatus.GlobalRollbackOnly) + { + if (defaultStatus.Debug) + { + log.Debug("Global transaction is marked as rollback-only but transactional code requested commit"); + } + ProcessRollback(defaultStatus); + // Throw UnexpectedRollbackException only at outermost transaction boundary + // or if explicitly asked to. + if (defaultStatus.IsNewTransaction || FailEarlyOnGlobalRollbackOnly) + { + throw new UnexpectedRollbackException( + "Transaction rolled back because it has been marked as rollback-only"); + } + return; + } + ProcessCommit(defaultStatus); + + } + + protected virtual bool ShouldCommitOnGlobalRollbackOnly + { + get { return false; } + } + + private void ProcessCommit(DefaultTransactionStatus status) + { + try + { + bool beforeCompletionInvoked = false; + try + { + TriggerBeforeCommit(status); + TriggerBeforeCompletion(status); + beforeCompletionInvoked = true; + bool globalRollbackOnly = false; + if (status.IsNewTransaction || FailEarlyOnGlobalRollbackOnly) + { + globalRollbackOnly = status.GlobalRollbackOnly; + } + if (status.HasSavepoint) + { + status.ReleaseHeldSavepoint(); + } + else if (status.IsNewTransaction) + { + DoCommit(status); + } + // Throw UnexpectedRollbackException if we have a global rollback-only + // marker but still didn't get a corresponding exception from commit. + if (globalRollbackOnly) + { + throw new UnexpectedRollbackException( + "Transaction silently rolled back because it has been marked as rollback-only"); + } + } + catch (UnexpectedRollbackException) + { + TriggerAfterCompletion(status, TransactionSynchronizationStatus.Rolledback); + throw; + } + catch (TransactionException ex) + { + if (RollbackOnCommitFailure) + { + DoRollbackOnCommitException(status, ex); + } + else + { + TriggerAfterCompletion(status, TransactionSynchronizationStatus.Unknown); + } + throw; + } + catch (Exception ex) + { + if (!beforeCompletionInvoked) + { + TriggerBeforeCompletion(status); + } + DoRollbackOnCommitException(status, ex); + throw; + } + // Trigger AfterCommit callbacks, with an exception thrown there + // propagated to callers but the transaction still considered as commited. + try + { + TriggerAfterCommit(status); + } + finally + { + TriggerAfterCompletion(status, TransactionSynchronizationStatus.Committed); + } + } + finally + { + CleanupAfterCompletion(status); + } + } + + private void TriggerAfterCommit(DefaultTransactionStatus status) + { + if (status.NewSynchronization) + { + if (status.Debug) + { + log.Debug("Trigger AfterCommit Synchronization"); + } + IList synchronizations = TransactionSynchronizationManager.Synchronizations; + foreach (ITransactionSynchronization currentTxnSynchronization in synchronizations) + { + try + { + currentTxnSynchronization.AfterCommit(); + } + catch (Exception e) + { + log.Error("TransactionSynchronization.AfterCommit thew exception", e); + } + } + } + } + + /// + /// Roll back the given transaction, with regard to its status. + /// + /// + ///

+ /// This implementation handles participating in existing transactions. + ///

+ ///

+ /// Delegates to + /// , + /// and + /// . + ///

+ ///

+ /// If the transaction wasn't a new one, just set it rollback-only + /// to take part in the surrounding transaction properly. + ///

+ ///
+ /// + /// ITransactionStatusObject returned by the + /// () method. + /// + /// + /// In case of system errors. + /// + public void Rollback(ITransactionStatus transactionStatus) + { + if (transactionStatus.Completed) + { + throw new IllegalTransactionStateException( + "Transaction is already completed - do not call commit or rollback more than once per transaction"); + } + DefaultTransactionStatus defaultStatus = (DefaultTransactionStatus)transactionStatus; + ProcessRollback(defaultStatus); + } + + private void ProcessRollback(DefaultTransactionStatus status) + { + try + { + try + { + TriggerBeforeCompletion(status); + if (status.HasSavepoint) + { + if (status.Debug) + { + log.Debug("Rolling back transaction to savepoint."); + } + status.RollbackToHeldSavepoint(); + } + else if (status.IsNewTransaction) + { + if (status.Debug) + { + log.Debug("Initiating transaction rollback"); + } + DoRollback(status); + } + else if (status.HasTransaction()) + { + if (status.LocalRollbackOnly) + { + if (status.Debug) + { + log.Debug("Participating transaction failed - marking existing transaction as rollback-only"); + } + } + DoSetRollbackOnly(status); + } + else + { + log.Debug("Should roll back transaction but cannot - no transaction available."); + } + } + catch (Exception) + { + TriggerAfterCompletion(status, TransactionSynchronizationStatus.Unknown); + throw; + } + TriggerAfterCompletion(status, TransactionSynchronizationStatus.Rolledback); + } + finally + { + CleanupAfterCompletion(status); + } + } + + #endregion + + #region Protected Method + + private DefaultTransactionStatus NewTransactionStatus(ITransactionDefinition definition, + object transaction, bool newTransaction, + bool newSynchronization, bool debug, + object suspendedResources) + { + bool actualNewSynchronization = newSynchronization && + !TransactionSynchronizationManager.SynchronizationActive; + // if (actualNewSynchronization) + // { + // TransactionSynchronizationManager.ActualTransactionActive = (transaction != null); + // TransactionSynchronizationManager.CurrentTransactionIsolationLevel = + // definition.TransactionIsolationLevel; + // TransactionSynchronizationManager.CurrentTransactionReadOnly = definition.ReadOnly; + // TransactionSynchronizationManager.CurrentTransactionName = definition.Name; + // TransactionSynchronizationManager.InitSynchronization(); + // } + return + new DefaultTransactionStatus(transaction, newTransaction, actualNewSynchronization, definition.ReadOnly, debug, + suspendedResources); + } + + /// + /// Determines the timeout to use for the given definition. Will fall back to this manager's default + /// timeout if the transaction definition doesn't specify a non-default value. + /// + /// The transaction definition. + /// the actual timeout to use. + protected int DetermineTimeout(ITransactionDefinition definition) + { + if (definition.TransactionTimeout != DefaultTransactionDefinition.TIMEOUT_DEFAULT) + { + return definition.TransactionTimeout; + } + return _defaultTimeout; + } + + #endregion + + #region Private Methods + + + + + + /// + /// Suspend the given transaction. Suspends transaction synchronization first, + /// then delegates to the doSuspend template method. + /// + /// the current transaction object + /// an object that holds suspended resources + private object Suspend(object transaction) + { + if (TransactionSynchronizationManager.SynchronizationActive) + { + IList suspendedSynchronizations = DoSuspendSynchronization(); + + try + { + object suspendedResources = null; + if (transaction != null) + { + suspendedResources = DoSuspend(transaction); + } + + string name = TransactionSynchronizationManager.CurrentTransactionName; + TransactionSynchronizationManager.CurrentTransactionName = null; + bool readOnly = TransactionSynchronizationManager.CurrentTransactionReadOnly; + TransactionSynchronizationManager.CurrentTransactionReadOnly = false; + IsolationLevel isolationLevel = TransactionSynchronizationManager.CurrentTransactionIsolationLevel; + TransactionSynchronizationManager.CurrentTransactionIsolationLevel = IsolationLevel.Unspecified; + bool wasActive = TransactionSynchronizationManager.ActualTransactionActive; + TransactionSynchronizationManager.ActualTransactionActive = false; + + + return new SuspendedResourcesHolder(suspendedSynchronizations, suspendedResources, + name, readOnly, isolationLevel, wasActive); + + } + catch (TransactionException) + { + // DoSuspend failed - original transaction is still active + DoResumeSynchronization(suspendedSynchronizations); + throw; + } + } + else if (transaction != null) + { + // Transaction active but no synchronization active. + object suspendedResources = DoSuspend(transaction); + return new SuspendedResourcesHolder(suspendedResources); + } + else + { + // Neither transaction nor synchronization active. + return null; + } + + } + + private IList DoSuspendSynchronization() + { + IList suspendedSynchronizations = TransactionSynchronizationManager.Synchronizations; + foreach (ITransactionSynchronization currentTxnSynchronization in suspendedSynchronizations) + { + currentTxnSynchronization.Suspend(); + } + TransactionSynchronizationManager.ClearSynchronization(); + return suspendedSynchronizations; + } + + private void DoResumeSynchronization(IList suspendedSynchronizations) + { + TransactionSynchronizationManager.InitSynchronization(); + foreach (ITransactionSynchronization currentTxnSynchronization in suspendedSynchronizations) + { + currentTxnSynchronization.Resume(); + TransactionSynchronizationManager.RegisterSynchronization(currentTxnSynchronization); + } + } + + /// + /// Resume the given transaction. Delegates to the doResume template method + /// first, then resuming transaction synchronization. + /// + /// the current transaction object + /// the object that holds suspended resources, as returned by suspend + private void Resume(object transaction, object suspendedResources) + { + SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder)suspendedResources; + if (resourcesHolder != null) + { + object suspendedResourcesObject = resourcesHolder.SuspendedResources; + if (suspendedResourcesObject != null) + { + DoResume(transaction, suspendedResourcesObject); + } + IList suspendedSynchronizations = resourcesHolder.SuspendedSynchronizations; + if (suspendedSynchronizations != null) + { + TransactionSynchronizationManager.ActualTransactionActive = resourcesHolder.WasActive; + TransactionSynchronizationManager.CurrentTransactionIsolationLevel = resourcesHolder.IsolationLevel; + TransactionSynchronizationManager.CurrentTransactionReadOnly = resourcesHolder.ReadOnly; + TransactionSynchronizationManager.CurrentTransactionName = resourcesHolder.Name; + DoResumeSynchronization(suspendedSynchronizations); + } + + } + } + + + + /// + /// Invoke doRollback, handling rollback exceptions properly. + /// + /// object representing the transaction + /// the thrown application exception or error + /// + /// in case of a rollback error + /// + private void DoRollbackOnCommitException(DefaultTransactionStatus status, Exception exception) + { + try + { + if (status.IsNewTransaction) + { + if (status.Debug) + { + log.Debug("Initiating transaction rollback on commit exception."); + } + DoRollback(status); + } + else if (status.HasTransaction()) + { + if (status.Debug) + { + log.Debug("Marking existing transaction as rollback-only after commit exception", exception); + } + DoSetRollbackOnly(status); + } + } + catch (Exception) + { + //TODO investigate rollback behavior... + log.Error("Commit exception overridden by rollback exception", exception); + TriggerAfterCompletion(status, TransactionSynchronizationStatus.Unknown); + throw; + } + TriggerAfterCompletion(status, TransactionSynchronizationStatus.Rolledback); + } + + /// + /// Trigger beforeCommit callback. + /// + /// object representing the transaction + private void TriggerBeforeCommit(DefaultTransactionStatus status) + { + if (status.NewSynchronization) + { + IList synchronizations = TransactionSynchronizationManager.Synchronizations; + foreach (ITransactionSynchronization currentTxnSynchronization in synchronizations) + { + currentTxnSynchronization.BeforeCommit(status.ReadOnly); + } + } + } + + /// + /// Trigger beforeCompletion callback. + /// + /// object representing the transaction + private void TriggerBeforeCompletion(DefaultTransactionStatus status) + { + if (status.NewSynchronization) + { + if (status.Debug) + { + log.Debug("Trigger BeforeCompletion Synchronization"); + } + IList synchronizations = TransactionSynchronizationManager.Synchronizations; + foreach (ITransactionSynchronization synchronization in synchronizations) + { + try + { + synchronization.BeforeCompletion(); + } + catch (Exception e) + { + log.Error("TransactionSynchronization.BeforeCompletion threw exception", e); + } + } + } + } + + /// + /// Trigger afterCompletion callback, handling exceptions properly. + /// + /// object representing the transaction + /// + /// Completion status according to + /// + private void TriggerAfterCompletion(DefaultTransactionStatus status, TransactionSynchronizationStatus completionStatus) + { + if (status.NewSynchronization) + { + IList synchronizations = TransactionSynchronizationManager.Synchronizations; + if (!status.HasTransaction() || status.IsNewTransaction) + { + if (status.Debug) + { + log.Debug("Triggering afterCompletion synchronization"); + } + InvokeAfterCompletion(synchronizations, completionStatus); + } + else + { + //TODO investigate parallel of JTA/System.Txs log.Info("Transaction controlled outside of spring tx manager."); - RegisterAfterCompletionWithExistingTransaction(status.Transaction, synchronizations); - } - } - } - - private void InvokeAfterCompletion(IList synchronizations, TransactionSynchronizationStatus status) - { - foreach (ITransactionSynchronization synchronization in synchronizations) - { - try - { - synchronization.AfterCompletion(status); - } catch (Exception e) - { - log.Error("TransactionSynchronization.AfterCompletion threw exception", e); - } - } - } - - /// - /// Clean up after completion, clearing synchronization if necessary, - /// and invoking doCleanupAfterCompletion. - /// - /// object representing the transaction - private void CleanupAfterCompletion(DefaultTransactionStatus status) - { - status.Completed = true; - if (status.NewSynchronization) - { - TransactionSynchronizationManager.Clear(); - } - if (status.IsNewTransaction) - { - DoCleanupAfterCompletion(status.Transaction); - } - if (status.SuspendedResources != null) - { - if (status.Debug) - { - log.Debug("Resuming suspended transaction"); - } - Resume(status.Transaction, status.SuspendedResources); - } - } - - #endregion - } + RegisterAfterCompletionWithExistingTransaction(status.Transaction, synchronizations); + } + } + } + + private void InvokeAfterCompletion(IList synchronizations, TransactionSynchronizationStatus status) + { + foreach (ITransactionSynchronization synchronization in synchronizations) + { + try + { + synchronization.AfterCompletion(status); + } + catch (Exception e) + { + log.Error("TransactionSynchronization.AfterCompletion threw exception", e); + } + } + } + + /// + /// Clean up after completion, clearing synchronization if necessary, + /// and invoking doCleanupAfterCompletion. + /// + /// object representing the transaction + private void CleanupAfterCompletion(DefaultTransactionStatus status) + { + status.Completed = true; + if (status.NewSynchronization) + { + TransactionSynchronizationManager.Clear(); + } + if (status.IsNewTransaction) + { + DoCleanupAfterCompletion(status.Transaction); + } + if (status.SuspendedResources != null) + { + if (status.Debug) + { + log.Debug("Resuming suspended transaction"); + } + Resume(status.Transaction, status.SuspendedResources); + } + } + + #endregion + } } \ No newline at end of file diff --git a/src/Spring/Spring.Data/Transaction/Support/TransactionSynchronizationManager.cs b/src/Spring/Spring.Data/Transaction/Support/TransactionSynchronizationManager.cs index 91b51fcd..6ec964cb 100644 --- a/src/Spring/Spring.Data/Transaction/Support/TransactionSynchronizationManager.cs +++ b/src/Spring/Spring.Data/Transaction/Support/TransactionSynchronizationManager.cs @@ -1,492 +1,492 @@ -#region License - -/* - * Copyright 2002-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. - */ - -#endregion - -using System; -using System.Collections; -using System.Data; -using System.Globalization; -using System.Threading; -using Spring.Core; -using Spring.Threading; -using Spring.Util; - -namespace Spring.Transaction.Support -{ - /// - /// Internal class that manages resources and transaction synchronizations per thread. - /// - /// - /// Supports one resource per key without overwriting, i.e. a resource needs to - /// be removed before a new one can be set for the same key. - /// Supports a list of transaction synchronizations if synchronization is active. - ///

- /// Resource management code should check for thread-bound resources via GetResource(). - /// It is normally not supposed - /// to bind resources to threads, as this is the responsiblity of transaction managers. - /// A further option is to lazily bind on first use if transaction synchronization - /// is active, for performing transactions that span an arbitrary number of resources. - ///

- ///

- /// Transaction synchronization must be activated and deactivated by a transaction - /// manager via - /// InitSynchronization - /// and - /// ClearSynchronization. - /// This is automatically supported by - /// . - ///

- ///

- /// Resource management code should only register synchronizations when this - /// manager is active, and perform resource cleanup immediately else. - /// If transaction synchronization isn't active, there is either no current - /// transaction, or the transaction manager doesn't support synchronizations. - ///

- /// Note that this class uses following naming convention for the - /// named 'data slots' for storage of thread local data, 'Spring.Transaction:Name' - /// where Name is either - ///
- /// Juergen Hoeller - /// Griffin Caprio (.NET) - /// Mark Pollack (.NET) - public sealed class TransactionSynchronizationManager - { - #region Logging - - private static readonly Common.Logging.ILog LOG = Common.Logging.LogManager.GetLogger(typeof (TransactionSynchronizationManager)); - - #endregion - - #region Fields - private static readonly string syncsDataSlotName = "Spring.Transactions:syncList"; - - private static readonly string resourcesDataSlotName = "Spring.Transactions:resources"; - - private static readonly string currentTxReadOnlyDataSlotName = "Spring.Transactions:currentTxReadOnly"; - - private static readonly string currentTxNameDataSlotName = "Spring.Transactions:currentTxName"; - - private static readonly string currentTxIsolationLevelDataSlotName = "Spring.Transactions:currentTxIsolationLevel"; - - private static readonly string actualTxActiveDataSlotName = "Spring.Transactions:actualTxActive"; - - private static IComparer syncComparer = new OrderComparator(); - - #endregion - - #region Management of transaction-associated resource handles - /// - /// Return all resources that are bound to the current thread. - /// - /// Main for debugging purposes. Resource manager should always - /// invoke HasResource for a specific resource key that they are interested in. - /// - /// IDictionary with resource keys and resource objects or empty - /// dictionary if none is bound. - public static IDictionary ResourceDictionary - { - get - { - IDictionary resources = LogicalThreadContext.GetData(resourcesDataSlotName) as IDictionary; - if (resources != null) - { - //TODO add readonly wrapper in Spring.Collections. - return resources; - } - else - { - return new Hashtable(); - } - } - } - - /// - /// Check if there is a resource for the given key bound to the current thread. - /// - /// key to check - /// if there is a value bound to the current thread - public static bool HasResource(Object key) - { - AssertUtils.ArgumentNotNull(key, "Key must not be null"); - return ResourceDictionary.Contains(key); - } - - /// - /// Retrieve a resource for the given key that is bound to the current thread. - /// - /// key to check - /// a value bound to the current thread, or null if none. - public static object GetResource(Object key) - { - AssertUtils.ArgumentNotNull(key, "Key must not be null"); - IDictionary resources = LogicalThreadContext.GetData(resourcesDataSlotName) as IDictionary; - if (resources == null) - { - return null; - } - //Check for contains since indexer returning null behavior changes in 2.0 - if (!resources.Contains(key)) - { - return null; - } - object val = resources[key]; - - if (val != null && LOG.IsDebugEnabled) - { - LOG.Debug("Retrieved value [" + Describe(val) + "] for key [" + Describe(key) + "] bound to thread [" + - SystemUtils.ThreadId + "]"); - } - return val; - } - - /// - /// Bind the given resource for teh given key to the current thread - /// - /// key to bind the value to - /// value to bind - public static void BindResource(Object key, Object value) - { - AssertUtils.ArgumentNotNull(key, "Key value for thread local storage of transactional resources must not be null"); - AssertUtils.ArgumentNotNull(value, "Transactional resource to bind to thread local storage must not be null" ); - - IDictionary resources = LogicalThreadContext.GetData(resourcesDataSlotName) as IDictionary; - //Set thread local resource storage if not found - if (resources == null) - { - resources = new Hashtable(); - LogicalThreadContext.SetData(resourcesDataSlotName, resources); - } - if (resources.Contains(key)) - { - throw new InvalidOperationException("Already value [" + resources[key] + "] for key [" + key + - "] bound to thread [" + SystemUtils.ThreadId + "]"); - } - resources.Add(key, value); - if (LOG.IsDebugEnabled) - { - LOG.Debug("Bound value [" + Describe(value) + "] for key [" + Describe(key) + "] to thread [" + - SystemUtils.ThreadId + "]"); - } - } - - - /// - /// Unbind a resource for the given key from the current thread - /// - /// key to check - /// the previously bound value - /// if there is no value bound to the thread - public static object UnbindResource(Object key) - { - AssertUtils.ArgumentNotNull(key, "Key must not be null"); - - IDictionary resources = LogicalThreadContext.GetData(resourcesDataSlotName) as IDictionary; - if (resources == null || !resources.Contains(key)) - { - throw new InvalidOperationException("No value for key [" + key + "] bound to thread [" + - SystemUtils.ThreadId + "]"); - } - Object val = resources[key]; - resources.Remove(key); - if (resources.Count == 0) - { - LogicalThreadContext.FreeNamedDataSlot(resourcesDataSlotName); - } - if (LOG.IsDebugEnabled) - { - LOG.Debug("Removed value [" + Describe(val) + "] for key [" + Describe(key) + "] from thread [" + - SystemUtils.ThreadId + "]"); - } - return val; - } - - #endregion - - /// - /// Activate transaction synchronization for the current thread. - /// - /// - /// Called by transaction manager at the beginning of a transaction. - /// - /// - /// If synchronization is already active. - /// - public static void InitSynchronization() - { - if ( SynchronizationActive ) - { - throw new InvalidOperationException( "Cannot activate transaction synchronization - already active" ); - } - if (LOG.IsDebugEnabled) - { - LOG.Debug("Initializing transaction synchronization"); - } - ArrayList syncs = new ArrayList(); - LogicalThreadContext.SetData(syncsDataSlotName, syncs); - } - - /// - /// Deactivate transaction synchronization for the current thread. - /// - /// - /// Called by transaction manager on transaction cleanup. - /// - /// - /// If synchronization is not active. - /// - public static void ClearSynchronization() - { - if ( !SynchronizationActive ) - { - throw new InvalidOperationException( "Cannot deactivate transaction synchronization - not active" ); - } - if (LOG.IsDebugEnabled) - { - LOG.Debug("Clearing transaction synchronization"); - } - LogicalThreadContext.FreeNamedDataSlot(syncsDataSlotName); - } - - /// - /// Clears the entire transaction synchronization state for the current thread, registered - /// synchronizations as well as the various transaction characteristics. - /// - public static void Clear() - { - ClearSynchronization(); - CurrentTransactionName = null; - CurrentTransactionReadOnly = false; - CurrentTransactionIsolationLevel = IsolationLevel.Unspecified; - ActualTransactionActive = false; - } - - /// - /// Register a new transaction synchronization for the current thread. - /// - /// - /// Typically called by resource management code. - /// - /// - /// If synchronization is not active. - /// - public static void RegisterSynchronization( ITransactionSynchronization synchronization ) - { - AssertUtils.ArgumentNotNull(synchronization, "TransactionSynchronization must not be null"); - if ( !SynchronizationActive ) - { - throw new InvalidOperationException( "Transaction synchronization is not active" ); - } - ArrayList syncs = LogicalThreadContext.GetData(syncsDataSlotName) as ArrayList; - if (syncs != null) - { - object root = syncs.SyncRoot; - lock (root) - { - syncs.Add(synchronization); - } - } - } - - private static string Describe(object obj) - { - return obj == null ? "" : obj + "@" + obj.GetHashCode().ToString("X"); - } - - #region Properties - - /// - /// Return an unmodifiable list of all registered synchronizations - /// for the current thread. - /// - /// - /// A list of - /// instances. - /// - /// - /// If synchronization is not active. - /// - public static IList Synchronizations - { - get - { - if ( ! SynchronizationActive ) - { - throw new InvalidOperationException( "Transaction synchronization is not active" ); - } - ArrayList syncs = LogicalThreadContext.GetData(syncsDataSlotName) as ArrayList; - if (syncs != null) - { - // Sort lazily here, not in registerSynchronization. - object root = syncs.SyncRoot; - lock (root) - { - // #SPRNET-1160, tx Ben Rowlands - CollectionUtils.StableSortInPlace(syncs, syncComparer); +#region License + +/* + * Copyright 2002-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. + */ + +#endregion + +using System; +using System.Collections; +using System.Data; +using System.Globalization; +using System.Threading; +using Spring.Core; +using Spring.Threading; +using Spring.Util; + +namespace Spring.Transaction.Support +{ + /// + /// Internal class that manages resources and transaction synchronizations per thread. + /// + /// + /// Supports one resource per key without overwriting, i.e. a resource needs to + /// be removed before a new one can be set for the same key. + /// Supports a list of transaction synchronizations if synchronization is active. + ///

+ /// Resource management code should check for thread-bound resources via GetResource(). + /// It is normally not supposed + /// to bind resources to threads, as this is the responsiblity of transaction managers. + /// A further option is to lazily bind on first use if transaction synchronization + /// is active, for performing transactions that span an arbitrary number of resources. + ///

+ ///

+ /// Transaction synchronization must be activated and deactivated by a transaction + /// manager via + /// InitSynchronization + /// and + /// ClearSynchronization. + /// This is automatically supported by + /// . + ///

+ ///

+ /// Resource management code should only register synchronizations when this + /// manager is active, and perform resource cleanup immediately else. + /// If transaction synchronization isn't active, there is either no current + /// transaction, or the transaction manager doesn't support synchronizations. + ///

+ /// Note that this class uses following naming convention for the + /// named 'data slots' for storage of thread local data, 'Spring.Transaction:Name' + /// where Name is either + ///
+ /// Juergen Hoeller + /// Griffin Caprio (.NET) + /// Mark Pollack (.NET) + public sealed class TransactionSynchronizationManager + { + #region Logging + + private static readonly Common.Logging.ILog LOG = Common.Logging.LogManager.GetLogger(typeof (TransactionSynchronizationManager)); + + #endregion + + #region Fields + private static readonly string syncsDataSlotName = "Spring.Transactions:syncList"; + + private static readonly string resourcesDataSlotName = "Spring.Transactions:resources"; + + private static readonly string currentTxReadOnlyDataSlotName = "Spring.Transactions:currentTxReadOnly"; + + private static readonly string currentTxNameDataSlotName = "Spring.Transactions:currentTxName"; + + private static readonly string currentTxIsolationLevelDataSlotName = "Spring.Transactions:currentTxIsolationLevel"; + + private static readonly string actualTxActiveDataSlotName = "Spring.Transactions:actualTxActive"; + + private static IComparer syncComparer = new OrderComparator(); + + #endregion + + #region Management of transaction-associated resource handles + /// + /// Return all resources that are bound to the current thread. + /// + /// Main for debugging purposes. Resource manager should always + /// invoke HasResource for a specific resource key that they are interested in. + /// + /// IDictionary with resource keys and resource objects or empty + /// dictionary if none is bound. + public static IDictionary ResourceDictionary + { + get + { + IDictionary resources = LogicalThreadContext.GetData(resourcesDataSlotName) as IDictionary; + if (resources != null) + { + //TODO add readonly wrapper in Spring.Collections. + return resources; + } + else + { + return new Hashtable(); + } + } + } + + /// + /// Check if there is a resource for the given key bound to the current thread. + /// + /// key to check + /// if there is a value bound to the current thread + public static bool HasResource(Object key) + { + AssertUtils.ArgumentNotNull(key, "Key must not be null"); + return ResourceDictionary.Contains(key); + } + + /// + /// Retrieve a resource for the given key that is bound to the current thread. + /// + /// key to check + /// a value bound to the current thread, or null if none. + public static object GetResource(Object key) + { + AssertUtils.ArgumentNotNull(key, "Key must not be null"); + IDictionary resources = LogicalThreadContext.GetData(resourcesDataSlotName) as IDictionary; + if (resources == null) + { + return null; + } + //Check for contains since indexer returning null behavior changes in 2.0 + if (!resources.Contains(key)) + { + return null; + } + object val = resources[key]; + + if (val != null && LOG.IsDebugEnabled) + { + LOG.Debug("Retrieved value [" + Describe(val) + "] for key [" + Describe(key) + "] bound to thread [" + + SystemUtils.ThreadId + "]"); + } + return val; + } + + /// + /// Bind the given resource for teh given key to the current thread + /// + /// key to bind the value to + /// value to bind + public static void BindResource(Object key, Object value) + { + AssertUtils.ArgumentNotNull(key, "Key value for thread local storage of transactional resources must not be null"); + AssertUtils.ArgumentNotNull(value, "Transactional resource to bind to thread local storage must not be null" ); + + IDictionary resources = LogicalThreadContext.GetData(resourcesDataSlotName) as IDictionary; + //Set thread local resource storage if not found + if (resources == null) + { + resources = new Hashtable(); + LogicalThreadContext.SetData(resourcesDataSlotName, resources); + } + if (resources.Contains(key)) + { + throw new InvalidOperationException("Already value [" + resources[key] + "] for key [" + key + + "] bound to thread [" + SystemUtils.ThreadId + "]"); + } + resources.Add(key, value); + if (LOG.IsDebugEnabled) + { + LOG.Debug("Bound value [" + Describe(value) + "] for key [" + Describe(key) + "] to thread [" + + SystemUtils.ThreadId + "]"); + } + } + + + /// + /// Unbind a resource for the given key from the current thread + /// + /// key to check + /// the previously bound value + /// if there is no value bound to the thread + public static object UnbindResource(Object key) + { + AssertUtils.ArgumentNotNull(key, "Key must not be null"); + + IDictionary resources = LogicalThreadContext.GetData(resourcesDataSlotName) as IDictionary; + if (resources == null || !resources.Contains(key)) + { + throw new InvalidOperationException("No value for key [" + key + "] bound to thread [" + + SystemUtils.ThreadId + "]"); + } + Object val = resources[key]; + resources.Remove(key); + if (resources.Count == 0) + { + LogicalThreadContext.FreeNamedDataSlot(resourcesDataSlotName); + } + if (LOG.IsDebugEnabled) + { + LOG.Debug("Removed value [" + Describe(val) + "] for key [" + Describe(key) + "] from thread [" + + SystemUtils.ThreadId + "]"); + } + return val; + } + + #endregion + + /// + /// Activate transaction synchronization for the current thread. + /// + /// + /// Called by transaction manager at the beginning of a transaction. + /// + /// + /// If synchronization is already active. + /// + public static void InitSynchronization() + { + if ( SynchronizationActive ) + { + throw new InvalidOperationException( "Cannot activate transaction synchronization - already active" ); + } + if (LOG.IsDebugEnabled) + { + LOG.Debug("Initializing transaction synchronization"); + } + ArrayList syncs = new ArrayList(); + LogicalThreadContext.SetData(syncsDataSlotName, syncs); + } + + /// + /// Deactivate transaction synchronization for the current thread. + /// + /// + /// Called by transaction manager on transaction cleanup. + /// + /// + /// If synchronization is not active. + /// + public static void ClearSynchronization() + { + if ( !SynchronizationActive ) + { + throw new InvalidOperationException( "Cannot deactivate transaction synchronization - not active" ); + } + if (LOG.IsDebugEnabled) + { + LOG.Debug("Clearing transaction synchronization"); + } + LogicalThreadContext.FreeNamedDataSlot(syncsDataSlotName); + } + + /// + /// Clears the entire transaction synchronization state for the current thread, registered + /// synchronizations as well as the various transaction characteristics. + /// + public static void Clear() + { + ClearSynchronization(); + CurrentTransactionName = null; + CurrentTransactionReadOnly = false; + CurrentTransactionIsolationLevel = IsolationLevel.Unspecified; + ActualTransactionActive = false; + } + + /// + /// Register a new transaction synchronization for the current thread. + /// + /// + /// Typically called by resource management code. + /// + /// + /// If synchronization is not active. + /// + public static void RegisterSynchronization( ITransactionSynchronization synchronization ) + { + AssertUtils.ArgumentNotNull(synchronization, "TransactionSynchronization must not be null"); + if ( !SynchronizationActive ) + { + throw new InvalidOperationException( "Transaction synchronization is not active" ); + } + ArrayList syncs = LogicalThreadContext.GetData(syncsDataSlotName) as ArrayList; + if (syncs != null) + { + object root = syncs.SyncRoot; + lock (root) + { + syncs.Add(synchronization); + } + } + } + + private static string Describe(object obj) + { + return obj == null ? "" : obj + "@" + obj.GetHashCode().ToString("X"); + } + + #region Properties + + /// + /// Return an unmodifiable list of all registered synchronizations + /// for the current thread. + /// + /// + /// A list of + /// instances. + /// + /// + /// If synchronization is not active. + /// + public static IList Synchronizations + { + get + { + if ( ! SynchronizationActive ) + { + throw new InvalidOperationException( "Transaction synchronization is not active" ); + } + ArrayList syncs = LogicalThreadContext.GetData(syncsDataSlotName) as ArrayList; + if (syncs != null) + { + // Sort lazily here, not in registerSynchronization. + object root = syncs.SyncRoot; + lock (root) + { + // #SPRNET-1160, tx Ben Rowlands + CollectionUtils.StableSortInPlace(syncs, syncComparer); } - // Return unmodifiable snapshot, to avoid exceptions - // while iterating and invoking synchronization callbacks that in turn - // might register further synchronizations. - return ArrayList.ReadOnly(syncs); - } - else - { - return ArrayList.ReadOnly(new ArrayList()); - } - } - } - - /// - /// Return if transaction synchronization is active for the current thread. - /// - /// - /// Can be called before - /// InitSynchronization - /// to avoid unnecessary instance creation. - /// - public static bool SynchronizationActive - { - get - { - IList syncs = LogicalThreadContext.GetData(syncsDataSlotName) as IList; - return syncs != null; - } - } - - /// - /// Gets or sets a value indicating whether the - /// current transaction is read only. - /// - /// - /// Called by transaction manager on transaction begin and on cleanup. - /// Return whether the current transaction is marked as read-only. - /// To be called by resource management code when preparing a newly - /// created resource (for example, a Hibernate Session). - ///

Note that transaction synchronizations receive the read-only flag - /// as argument for the beforeCommit callback, to be able - /// to suppress change detection on commit. The present method is meant - /// to be used for earlier read-only checks, for example to set the - /// flush mode of a Hibernate Session to FlushMode.Never upfront. - ///

- ///
- /// - /// true if current transaction read only; otherwise, false. - /// - public static bool CurrentTransactionReadOnly - { - get - { - return LogicalThreadContext.GetData(currentTxReadOnlyDataSlotName) != null; - } - set - { - if (value) - { - LogicalThreadContext.SetData(currentTxReadOnlyDataSlotName, true); - } - else - { - LogicalThreadContext.FreeNamedDataSlot(currentTxReadOnlyDataSlotName); - } - - } - } - - /// - /// Gets or sets the name of the current transaction, if any. - /// - /// Called by the transaction manager on transaction begin and on cleanup. - /// To be called by resource management code for optimizations per use case, for - /// example to optimize fetch strategies for specific named transactions. - /// The name of the current transactio or null if none set. - public static string CurrentTransactionName - { - get - { - return LogicalThreadContext.GetData(currentTxNameDataSlotName) as string; - } - set - { - LogicalThreadContext.SetData(currentTxNameDataSlotName, value); - } - } - - /// - /// Gets or sets a value indicating whether there currently is an actual transaction - /// active. - /// - /// This indicates wheter the current thread is associated with an actual - /// transaction rather than just with active transaction synchronization. - /// Called by the transaction manager on transaction begin and on cleanup. - /// To be called by resource management code that wants to discriminate between - /// active transaction synchronization (with or without backing resource transaction; - /// also on PROPAGATION_SUPPORTS) and an actual transaction being active; on - /// PROPAGATION_REQUIRES, PROPAGATION_REQUIRES_NEW, etC) - /// - /// true if [actual transaction active]; otherwise, false. - /// - public static bool ActualTransactionActive - { - get - { - return LogicalThreadContext.GetData(actualTxActiveDataSlotName) != null; - } - set - { - if (value) - { - LogicalThreadContext.SetData(actualTxActiveDataSlotName, value); - } - else - { - LogicalThreadContext.FreeNamedDataSlot(actualTxActiveDataSlotName); - } - } - } - - - /// - /// Gets or sets the current transaction isolation level, if any. - /// - /// Called by the transaction manager on transaction begin and on cleanup. - /// The current transaction isolation level. If no current transaction is - /// active, retrun IsolationLevel.Unspecified - public static IsolationLevel CurrentTransactionIsolationLevel - { - get - { - object data = - LogicalThreadContext.GetData(currentTxIsolationLevelDataSlotName); - if (data != null) - { - return (IsolationLevel) data; - } - else - { - return IsolationLevel.Unspecified; - } - } - set - { - LogicalThreadContext.SetData(currentTxIsolationLevelDataSlotName, value); - } - } - #endregion - } -} + // Return unmodifiable snapshot, to avoid exceptions + // while iterating and invoking synchronization callbacks that in turn + // might register further synchronizations. + return ArrayList.ReadOnly(syncs); + } + else + { + return ArrayList.ReadOnly(new ArrayList()); + } + } + } + + /// + /// Return if transaction synchronization is active for the current thread. + /// + /// + /// Can be called before + /// InitSynchronization + /// to avoid unnecessary instance creation. + /// + public static bool SynchronizationActive + { + get + { + IList syncs = LogicalThreadContext.GetData(syncsDataSlotName) as IList; + return syncs != null; + } + } + + /// + /// Gets or sets a value indicating whether the + /// current transaction is read only. + /// + /// + /// Called by transaction manager on transaction begin and on cleanup. + /// Return whether the current transaction is marked as read-only. + /// To be called by resource management code when preparing a newly + /// created resource (for example, a Hibernate Session). + ///

Note that transaction synchronizations receive the read-only flag + /// as argument for the beforeCommit callback, to be able + /// to suppress change detection on commit. The present method is meant + /// to be used for earlier read-only checks, for example to set the + /// flush mode of a Hibernate Session to FlushMode.Never upfront. + ///

+ ///
+ /// + /// true if current transaction read only; otherwise, false. + /// + public static bool CurrentTransactionReadOnly + { + get + { + return LogicalThreadContext.GetData(currentTxReadOnlyDataSlotName) != null; + } + set + { + if (value) + { + LogicalThreadContext.SetData(currentTxReadOnlyDataSlotName, true); + } + else + { + LogicalThreadContext.FreeNamedDataSlot(currentTxReadOnlyDataSlotName); + } + + } + } + + /// + /// Gets or sets the name of the current transaction, if any. + /// + /// Called by the transaction manager on transaction begin and on cleanup. + /// To be called by resource management code for optimizations per use case, for + /// example to optimize fetch strategies for specific named transactions. + /// The name of the current transactio or null if none set. + public static string CurrentTransactionName + { + get + { + return LogicalThreadContext.GetData(currentTxNameDataSlotName) as string; + } + set + { + LogicalThreadContext.SetData(currentTxNameDataSlotName, value); + } + } + + /// + /// Gets or sets a value indicating whether there currently is an actual transaction + /// active. + /// + /// This indicates wheter the current thread is associated with an actual + /// transaction rather than just with active transaction synchronization. + /// Called by the transaction manager on transaction begin and on cleanup. + /// To be called by resource management code that wants to discriminate between + /// active transaction synchronization (with or without backing resource transaction; + /// also on PROPAGATION_SUPPORTS) and an actual transaction being active; on + /// PROPAGATION_REQUIRES, PROPAGATION_REQUIRES_NEW, etC) + /// + /// true if [actual transaction active]; otherwise, false. + /// + public static bool ActualTransactionActive + { + get + { + return LogicalThreadContext.GetData(actualTxActiveDataSlotName) != null; + } + set + { + if (value) + { + LogicalThreadContext.SetData(actualTxActiveDataSlotName, value); + } + else + { + LogicalThreadContext.FreeNamedDataSlot(actualTxActiveDataSlotName); + } + } + } + + + /// + /// Gets or sets the current transaction isolation level, if any. + /// + /// Called by the transaction manager on transaction begin and on cleanup. + /// The current transaction isolation level. If no current transaction is + /// active, retrun IsolationLevel.Unspecified + public static IsolationLevel CurrentTransactionIsolationLevel + { + get + { + object data = + LogicalThreadContext.GetData(currentTxIsolationLevelDataSlotName); + if (data != null) + { + return (IsolationLevel) data; + } + else + { + return IsolationLevel.Unspecified; + } + } + set + { + LogicalThreadContext.SetData(currentTxIsolationLevelDataSlotName, value); + } + } + #endregion + } +} diff --git a/test/Spring/Spring.Data.NHibernate.Tests/Data/NHibernate/HibernateTransactionManagerTests.cs b/test/Spring/Spring.Data.NHibernate.Tests/Data/NHibernate/HibernateTransactionManagerTests.cs index 6a30f7ff..ee71e93f 100644 --- a/test/Spring/Spring.Data.NHibernate.Tests/Data/NHibernate/HibernateTransactionManagerTests.cs +++ b/test/Spring/Spring.Data.NHibernate.Tests/Data/NHibernate/HibernateTransactionManagerTests.cs @@ -45,6 +45,29 @@ namespace Spring.Data.NHibernate [TestFixture] public class HibernateTransactionManagerTests { + private class TestableHibernateTransactionManager : HibernateTransactionManager + { + private IDbTransaction _stubbedTransactionWithExpectedConnection; + + public TestableHibernateTransactionManager() + { + } + + public TestableHibernateTransactionManager(ISessionFactory sessionFactory) : base(sessionFactory) + { + } + + public IDbTransaction StubbedTransactionThatReturnsExpectedConnection + { + set { _stubbedTransactionWithExpectedConnection = value; } + } + + protected override IDbTransaction GetIDbTransaction(ITransaction hibernateTx) + { + return _stubbedTransactionWithExpectedConnection; + } + } + private MockRepository mocks; [SetUp] @@ -88,6 +111,7 @@ namespace Spring.Data.NHibernate Assert.IsNotNull(sfProxy); HibernateTransactionManager tm = new HibernateTransactionManager(); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; tm.AdoExceptionTranslator = new FallbackExceptionTranslator(); tm.SessionFactory = sfProxy; tm.DbProvider = provider; @@ -95,19 +119,19 @@ namespace Spring.Data.NHibernate tt.TransactionIsolationLevel = IsolationLevel.Serializable; - Assert.IsTrue(!TransactionSynchronizationManager.HasResource(sfProxy),"Hasn't thread session"); - Assert.IsTrue(!TransactionSynchronizationManager.HasResource(provider), "Hasn't thread db provider"); - Assert.IsTrue(!TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active"); - Assert.IsTrue(!TransactionSynchronizationManager.ActualTransactionActive, "Actual transaction not active"); + Assert.IsFalse(TransactionSynchronizationManager.HasResource(sfProxy),"Hasn't thread session"); + Assert.IsFalse(TransactionSynchronizationManager.HasResource(provider), "Hasn't thread db provider"); + Assert.IsFalse(TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active"); + Assert.IsFalse(TransactionSynchronizationManager.ActualTransactionActive, "Actual transaction not active"); object result = tt.Execute(new TransactionCommitTxCallback(sfProxy, provider)); Assert.IsTrue(result == list, "Incorrect result list"); - Assert.IsTrue(!TransactionSynchronizationManager.HasResource(sfProxy), "Hasn't thread session"); - Assert.IsTrue(!TransactionSynchronizationManager.HasResource(provider), "Hasn't thread db provider"); - Assert.IsTrue(!TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active"); - Assert.IsTrue(!TransactionSynchronizationManager.ActualTransactionActive, "Actual transaction not active"); + Assert.IsFalse(TransactionSynchronizationManager.HasResource(sfProxy), "Hasn't thread session"); + Assert.IsFalse(TransactionSynchronizationManager.HasResource(provider), "Hasn't thread db provider"); + Assert.IsFalse(TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active"); + Assert.IsFalse(TransactionSynchronizationManager.ActualTransactionActive, "Actual transaction not active"); mocks.VerifyAll(); @@ -124,6 +148,7 @@ namespace Spring.Data.NHibernate ISessionFactory sessionFactory = (ISessionFactory)mocks.CreateMock(typeof(ISessionFactory)); ISession session = (ISession)mocks.CreateMock(typeof(ISession)); ITransaction transaction = (ITransaction)mocks.CreateMock(typeof(ITransaction)); + IDbTransaction adoTransaction = (IDbTransaction)mocks.CreateMock(typeof(IDbTransaction)); using (mocks.Ordered()) { @@ -132,6 +157,9 @@ namespace Spring.Data.NHibernate Expect.Call(session.BeginTransaction(IsolationLevel.ReadCommitted)).Return(transaction); Expect.Call(session.IsOpen).Return(true); + Expect.Call(adoTransaction.Connection).Return(connection); + LastCall.On(adoTransaction).Repeat.Once(); + transaction.Rollback(); LastCall.On(transaction).Repeat.Once(); @@ -139,11 +167,14 @@ namespace Spring.Data.NHibernate } mocks.ReplayAll(); - HibernateTransactionManager tm = new HibernateTransactionManager(sessionFactory); + TestableHibernateTransactionManager tm = new TestableHibernateTransactionManager(sessionFactory); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + tm.StubbedTransactionThatReturnsExpectedConnection = adoTransaction; + TransactionTemplate tt = new TransactionTemplate(tm); - Assert.IsTrue(!TransactionSynchronizationManager.HasResource(sessionFactory), "Hasn't thread session"); - Assert.IsTrue(!TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active"); + Assert.IsFalse(TransactionSynchronizationManager.HasResource(sessionFactory), "Hasn't thread session"); + Assert.IsFalse(TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active"); try { @@ -154,8 +185,8 @@ namespace Spring.Data.NHibernate } - Assert.IsTrue(!TransactionSynchronizationManager.HasResource(sessionFactory), "Hasn't thread session"); - Assert.IsTrue(!TransactionSynchronizationManager.HasResource(provider), "Hasn't thread db provider"); + Assert.IsFalse(TransactionSynchronizationManager.HasResource(sessionFactory), "Hasn't thread session"); + Assert.IsFalse(TransactionSynchronizationManager.HasResource(provider), "Hasn't thread db provider"); mocks.VerifyAll(); } @@ -167,6 +198,7 @@ namespace Spring.Data.NHibernate ISessionFactory sessionFactory = (ISessionFactory)mocks.CreateMock(typeof(ISessionFactory)); ISession session = (ISession)mocks.CreateMock(typeof(ISession)); ITransaction transaction = (ITransaction)mocks.CreateMock(typeof(ITransaction)); + IDbTransaction adoTransaction = (IDbTransaction)mocks.CreateMock(typeof(IDbTransaction)); using (mocks.Ordered()) { @@ -177,6 +209,10 @@ namespace Spring.Data.NHibernate Expect.Call(session.FlushMode).Return(FlushMode.Auto); session.Flush(); LastCall.On(session).Repeat.Once(); + + Expect.Call(adoTransaction.Connection).Return(connection); + LastCall.On(adoTransaction).Repeat.Once(); + transaction.Rollback(); LastCall.On(transaction).Repeat.Once(); Expect.Call(session.Close()).Return(null); @@ -184,14 +220,17 @@ namespace Spring.Data.NHibernate mocks.ReplayAll(); - HibernateTransactionManager tm = new HibernateTransactionManager(sessionFactory); + TestableHibernateTransactionManager tm = new TestableHibernateTransactionManager(sessionFactory); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + tm.StubbedTransactionThatReturnsExpectedConnection = adoTransaction; + TransactionTemplate tt = new TransactionTemplate(tm); - Assert.IsTrue(!TransactionSynchronizationManager.HasResource(sessionFactory), "Hasn't thread session"); + Assert.IsFalse(TransactionSynchronizationManager.HasResource(sessionFactory), "Shouldn't have a thread session"); tt.Execute(new TransactionRollbackOnlyTxCallback(sessionFactory)); - Assert.IsTrue(!TransactionSynchronizationManager.HasResource(sessionFactory), "Hasn't thread session"); + Assert.IsFalse(TransactionSynchronizationManager.HasResource(sessionFactory), "Shouldn't have a thread session"); mocks.VerifyAll(); @@ -222,6 +261,8 @@ namespace Spring.Data.NHibernate mocks.ReplayAll(); HibernateTransactionManager tm = new HibernateTransactionManager(sessionFactory); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); IList list = new ArrayList(); list.Add("test"); @@ -240,6 +281,7 @@ namespace Spring.Data.NHibernate ISessionFactory sessionFactory = (ISessionFactory) mocks.CreateMock(typeof (ISessionFactory)); ISession session = (ISession) mocks.CreateMock(typeof (ISession)); ITransaction transaction = (ITransaction) mocks.CreateMock(typeof (ITransaction)); + IDbTransaction adoTransaction = (IDbTransaction)mocks.CreateMock(typeof(IDbTransaction)); using (mocks.Ordered()) { @@ -249,6 +291,9 @@ namespace Spring.Data.NHibernate Expect.Call(session.IsOpen).Return(true); Expect.Call(session.FlushMode).Return(FlushMode.Auto); + Expect.Call(adoTransaction.Connection).Return(connection); + LastCall.On(adoTransaction).Repeat.Once(); + transaction.Rollback(); LastCall.On(transaction).Repeat.Once(); Expect.Call(session.Close()).Return(null); @@ -256,7 +301,10 @@ namespace Spring.Data.NHibernate mocks.ReplayAll(); - HibernateTransactionManager tm = new HibernateTransactionManager(sessionFactory); + TestableHibernateTransactionManager tm = new TestableHibernateTransactionManager(sessionFactory); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + tm.StubbedTransactionThatReturnsExpectedConnection = adoTransaction; + TransactionTemplate tt = new TransactionTemplate(tm); try { @@ -278,6 +326,7 @@ namespace Spring.Data.NHibernate ISessionFactory sessionFactory = (ISessionFactory)mocks.CreateMock(typeof(ISessionFactory)); ISession session = (ISession)mocks.CreateMock(typeof(ISession)); ITransaction transaction = (ITransaction)mocks.CreateMock(typeof(ITransaction)); + IDbTransaction adoTransaction = (IDbTransaction)mocks.CreateMock(typeof(IDbTransaction)); using (mocks.Ordered()) { @@ -286,6 +335,9 @@ namespace Spring.Data.NHibernate Expect.Call(session.BeginTransaction(IsolationLevel.ReadCommitted)).Return(transaction); Expect.Call(session.IsOpen).Return(true); + Expect.Call(adoTransaction.Connection).Return(connection); + LastCall.On(adoTransaction).Repeat.Once(); + transaction.Rollback(); LastCall.On(transaction).Repeat.Once(); Expect.Call(session.Close()).Return(null); @@ -293,7 +345,10 @@ namespace Spring.Data.NHibernate mocks.ReplayAll(); - HibernateTransactionManager tm = new HibernateTransactionManager(sessionFactory); + TestableHibernateTransactionManager tm = new TestableHibernateTransactionManager(sessionFactory); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + tm.StubbedTransactionThatReturnsExpectedConnection = adoTransaction; + TransactionTemplate tt = new TransactionTemplate(tm); IList list = new ArrayList(); list.Add("test"); @@ -345,6 +400,8 @@ namespace Spring.Data.NHibernate mocks.ReplayAll(); HibernateTransactionManager tm = new HibernateTransactionManager(sessionFactory); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.PropagationBehavior = TransactionPropagation.RequiresNew; @@ -389,6 +446,8 @@ namespace Spring.Data.NHibernate HibernateTransactionManager tm = new HibernateTransactionManager(sessionFactory); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.PropagationBehavior = TransactionPropagation.RequiresNew; @@ -431,6 +490,8 @@ namespace Spring.Data.NHibernate Assert.IsNotNull(sfProxy); HibernateTransactionManager tm = new HibernateTransactionManager(sessionFactory); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.PropagationBehavior = TransactionPropagation.Supports; @@ -485,6 +546,7 @@ namespace Spring.Data.NHibernate Assert.IsNotNull(sfProxy); HibernateTransactionManager tm = new HibernateTransactionManager(sessionFactory); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; TransactionTemplate tt = new TransactionTemplate(tm); tt.PropagationBehavior = TransactionPropagation.Supports; @@ -536,6 +598,8 @@ namespace Spring.Data.NHibernate ISessionFactory sessionFactory = (ISessionFactory)mocks.CreateMock(typeof(ISessionFactory)); ISession session = (ISession)mocks.CreateMock(typeof(ISession)); ITransaction transaction = (ITransaction)mocks.CreateMock(typeof(ITransaction)); + IDbTransaction adoTransaction = (IDbTransaction)mocks.CreateMock(typeof(IDbTransaction)); + Exception rootCause = null; using (mocks.Ordered()) { @@ -562,6 +626,9 @@ namespace Spring.Data.NHibernate LastCall.On(transaction).Throw(rootCause); } + Expect.Call(adoTransaction.Connection).Return(connection); + LastCall.On(adoTransaction).Repeat.Once(); + transaction.Rollback(); LastCall.On(transaction).Repeat.Once(); Expect.Call(session.Close()).Return(null); @@ -572,8 +639,10 @@ namespace Spring.Data.NHibernate mocks.ReplayAll(); - HibernateTransactionManager tm = new HibernateTransactionManager(sessionFactory); + TestableHibernateTransactionManager tm = new TestableHibernateTransactionManager(sessionFactory); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; tm.DbProvider = provider; + tm.StubbedTransactionThatReturnsExpectedConnection = adoTransaction; TransactionTemplate tt = new TransactionTemplate(tm); Assert.IsTrue(!TransactionSynchronizationManager.HasResource(sessionFactory), "Hasn't thread session"); diff --git a/test/Spring/Spring.Data.NHibernate30.Integration.Tests/Spring.Data.NHibernate30.Integration.Tests.2010.csproj b/test/Spring/Spring.Data.NHibernate30.Integration.Tests/Spring.Data.NHibernate30.Integration.Tests.2010.csproj new file mode 100644 index 00000000..c800a062 --- /dev/null +++ b/test/Spring/Spring.Data.NHibernate30.Integration.Tests/Spring.Data.NHibernate30.Integration.Tests.2010.csproj @@ -0,0 +1,215 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {93FED0CE-0B01-43AF-8CB1-244CC7C3308B} + Library + Properties + Spring + Spring.Data.NHibernate30.Integration.Tests + v3.5 + + + 3.5 + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + + true + full + false + ..\..\..\build\VS.Net.2008\Spring.Data.NHibernate30.Integration.Tests\Debug\ + TRACE;DEBUG;NET_2_0,NH_2_0,NH_2_1 + prompt + 4 + AllRules.ruleset + + + pdbonly + true + ..\..\..\build\VS.Net.2008\Spring.Data.NHibernate30.Integration.Tests\Release\ + TRACE;NET_2_0,NH_2_0,NH_2_1 + prompt + 4 + AllRules.ruleset + + + + False + ..\..\..\lib\Net\2.0\Common.Logging.dll + + + False + ..\..\..\lib\NHibernate30\net\3.5\Iesi.Collections.dll + + + False + ..\..\..\lib\NHibernate30\net\3.5\log4net.dll + + + False + ..\..\..\lib\NHibernate30\net\3.5\NHibernate.dll + + + False + ..\..\..\lib\Net\2.0\nunit.framework.dll + + + + + + + + + Data\NHibernate\AccountCreditDao.cs + + + Data\NHibernate\AccountDebitDao.cs + + + Data\NHibernate\AccountManager.cs + + + Data\NHibernate\AuditDao.cs + + + Data\NHibernate\Credit.cs + + + Data\NHibernate\DbProviderTemplateTests.cs + + + Data\NHibernate\Debit.cs + + + Data\NHibernate\IAccountCreditDao.cs + + + Data\NHibernate\IAccountDebitDao.cs + + + Data\NHibernate\IAccountManager.cs + + + Data\NHibernate\IAuditDao.cs + + + Data\NHibernate\ITestObjectDao.cs + + + Data\NHibernate\MultipleDbTests.cs + + + Data\NHibernate\NativeNHTestObjectDao.cs + + + Data\NHibernate\NativeNHTests.cs + + + Data\NHibernate\NHDAOTests.cs + + + Data\NHibernate\NHTestObjectDao.cs + + + Data\NHibernate\TemplateTests.cs + + + Data\NHibernate\TestObject.cs + + + Data\NHibernate\HibernateTxScopeTransactionManagerTests.cs + + + + + + Data\NHibernate\creditdebit.sql + + + + + + {3A3A4E65-45A6-4B20-B460-0BEDC302C02C} + Spring.Aop.2010 + + + {710961A3-0DF4-49E4-A26E-F5B9C044AC84} + Spring.Core.2010 + + + {009247FE-CBAD-40FF-ADC3-D7F28B270071} + Spring.Data.NHibernate30.2010 + + + {AE00E5AB-C39A-436F-86D2-33BFE33E2E40} + Spring.Data.2010 + + + + + + + + + + + + + + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + true + + + False + Windows Installer 3.1 + true + + + + + Data\NHibernate\HibernateTxScopeTransactionManagerTests.xml + + + + + + echo "Copying .xml files for tests" +xcopy "$(ProjectDir)Data" ..\..\..\..\build\VS.Net.2008\Spring.Data.NHibernate30.Integration.Tests\$(ConfigurationName)\ /y /s /q /d +xcopy "$(ProjectDir)$(TargetFileName).config" ..\..\..\..\build\VS.Net.2008\Spring.Data.NHibernate30.Integration.Tests\$(ConfigurationName)\ /y /s /q + + \ No newline at end of file diff --git a/test/Spring/Spring.Data.Tests/Data/AdoPlatformTransactionManagerTests.cs b/test/Spring/Spring.Data.Tests/Data/AdoPlatformTransactionManagerTests.cs index 04dcb7e5..f4c6755f 100644 --- a/test/Spring/Spring.Data.Tests/Data/AdoPlatformTransactionManagerTests.cs +++ b/test/Spring/Spring.Data.Tests/Data/AdoPlatformTransactionManagerTests.cs @@ -86,6 +86,7 @@ namespace Spring.Data mocks.ReplayAll(); AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; TransactionTemplate tt = new TransactionTemplate(tm); @@ -127,6 +128,8 @@ namespace Spring.Data mocks.ReplayAll(); AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider"); @@ -171,9 +174,10 @@ namespace Spring.Data mocks.ReplayAll(); AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; - Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider"); - Assert.IsTrue(!TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active"); + Assert.IsFalse(TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider"); + Assert.IsFalse(TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active"); ITransactionStatus ts = tm.GetTransaction(new DefaultTransactionDefinition()); TestTransactionSynchronization synch = @@ -201,7 +205,7 @@ namespace Spring.Data Assert.IsTrue(outerTransactionBoundaryReached); } - Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider"); + Assert.IsFalse(TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider"); Assert.IsFalse(synch.beforeCommitCalled); Assert.IsTrue(synch.beforeCompletionCalled); Assert.IsFalse(synch.afterCommitCalled); @@ -249,6 +253,8 @@ namespace Spring.Data mocks.ReplayAll(); AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.PropagationBehavior = TransactionPropagation.RequiresNew; @@ -293,10 +299,14 @@ namespace Spring.Data mocks.ReplayAll(); AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.PropagationBehavior = TransactionPropagation.RequiresNew; AdoPlatformTransactionManager tm2 = new AdoPlatformTransactionManager(dbProvider2); + tm2.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt2 = new TransactionTemplate(tm2); tt2.PropagationBehavior = TransactionPropagation.RequiresNew; @@ -342,6 +352,8 @@ namespace Spring.Data mocks.ReplayAll(); AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.PropagationBehavior = TransactionPropagation.RequiresNew; @@ -393,6 +405,8 @@ namespace Spring.Data mocks.ReplayAll(); AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.PropagationBehavior = TransactionPropagation.RequiresNew; Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider"); @@ -428,6 +442,8 @@ namespace Spring.Data mocks.ReplayAll(); AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.PropagationBehavior = TransactionPropagation.RequiresNew; Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider"); @@ -475,6 +491,8 @@ namespace Spring.Data mocks.ReplayAll(); AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.PropagationBehavior = TransactionPropagation.Supports; Assert.IsTrue(!TransactionSynchronizationManager.HasResource(dbProvider), "Hasn't thread db provider"); @@ -513,6 +531,8 @@ namespace Spring.Data mocks.ReplayAll(); AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.PropagationBehavior = TransactionPropagation.RequiresNew; tt.TransactionIsolationLevel = IsolationLevel.Serializable; @@ -573,6 +593,8 @@ namespace Spring.Data mocks.ReplayAll(); AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.TransactionTimeout = timeout; @@ -616,6 +638,8 @@ namespace Spring.Data mocks.ReplayAll(); AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); try { @@ -661,6 +685,8 @@ namespace Spring.Data mocks.ReplayAll(); AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); try @@ -703,6 +729,8 @@ namespace Spring.Data mocks.ReplayAll(); AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + tm.RollbackOnCommitFailure = true; TransactionTemplate tt = new TransactionTemplate(tm); @@ -747,6 +775,8 @@ namespace Spring.Data mocks.ReplayAll(); AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); try @@ -778,6 +808,8 @@ namespace Spring.Data mocks.ReplayAll(); AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.PropagationBehavior = TransactionPropagation.Supports; @@ -797,6 +829,8 @@ namespace Spring.Data mocks.ReplayAll(); AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.PropagationBehavior = TransactionPropagation.NotSupported; @@ -816,6 +850,8 @@ namespace Spring.Data mocks.ReplayAll(); AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.PropagationBehavior = TransactionPropagation.Never; @@ -852,6 +888,8 @@ namespace Spring.Data mocks.ReplayAll(); AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.PropagationBehavior = TransactionPropagation.Nested; @@ -896,6 +934,8 @@ namespace Spring.Data mocks.ReplayAll(); AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.PropagationBehavior = TransactionPropagation.Nested; @@ -941,6 +981,8 @@ namespace Spring.Data mocks.ReplayAll(); AdoPlatformTransactionManager tm = new AdoPlatformTransactionManager(dbProvider); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.PropagationBehavior = TransactionPropagation.Nested; diff --git a/test/Spring/Spring.Data.Tests/Data/Core/ServiceDomainTransactionManagerTests.cs b/test/Spring/Spring.Data.Tests/Data/Core/ServiceDomainTransactionManagerTests.cs index e72755c1..15fae3e3 100644 --- a/test/Spring/Spring.Data.Tests/Data/Core/ServiceDomainTransactionManagerTests.cs +++ b/test/Spring/Spring.Data.Tests/Data/Core/ServiceDomainTransactionManagerTests.cs @@ -78,8 +78,10 @@ namespace Spring.Data.Core mocks.ReplayAll(); - IPlatformTransactionManager tm = new ServiceDomainPlatformTransactionManager(txAdapter); + ServiceDomainPlatformTransactionManager tm = new ServiceDomainPlatformTransactionManager(txAdapter); TransactionTemplate tt = new TransactionTemplate(tm); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + tt.Execute(new TransactionDelegate(TransactionCommitMethod)); Assert.IsFalse(TransactionSynchronizationManager.SynchronizationActive); @@ -117,7 +119,9 @@ namespace Spring.Data.Core mocks.ReplayAll(); - IPlatformTransactionManager tm = new ServiceDomainPlatformTransactionManager(txAdapter); + ServiceDomainPlatformTransactionManager tm = new ServiceDomainPlatformTransactionManager(txAdapter); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); Assert.IsTrue(!TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active"); @@ -183,7 +187,9 @@ namespace Spring.Data.Core mocks.ReplayAll(); - IPlatformTransactionManager tm = new ServiceDomainPlatformTransactionManager(txAdapter); + ServiceDomainPlatformTransactionManager tm = new ServiceDomainPlatformTransactionManager(txAdapter); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.PropagationBehavior = TransactionPropagation.RequiresNew; tt.Execute(new PropagationRequiresNewWithExistingTransactionCallbackSD(tt)); diff --git a/test/Spring/Spring.Data.Tests/Data/Core/TxScopeTransactionManagerIntegrationTests.cs b/test/Spring/Spring.Data.Tests/Data/Core/TxScopeTransactionManagerIntegrationTests.cs index 63763334..9c26fd59 100644 --- a/test/Spring/Spring.Data.Tests/Data/Core/TxScopeTransactionManagerIntegrationTests.cs +++ b/test/Spring/Spring.Data.Tests/Data/Core/TxScopeTransactionManagerIntegrationTests.cs @@ -65,6 +65,8 @@ namespace Spring.Data.Core public void Commit() { TxScopeTransactionManager tm = new TxScopeTransactionManager(); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); //tt.Name = "txName"; @@ -91,6 +93,8 @@ namespace Spring.Data.Core public void TransactionInformation() { TxScopeTransactionManager tm = new TxScopeTransactionManager(); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.TransactionIsolationLevel = System.Data.IsolationLevel.ReadUncommitted; tt.Execute(TransactionInformationTxDelegate); @@ -121,6 +125,8 @@ namespace Spring.Data.Core TxScopeTransactionManager tm = new TxScopeTransactionManager(); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.TransactionTimeout = 10; tt.Name = "txName"; diff --git a/test/Spring/Spring.Data.Tests/Data/Core/TxScopeTransactionManagerTests.cs b/test/Spring/Spring.Data.Tests/Data/Core/TxScopeTransactionManagerTests.cs index 946d8149..47bab5a3 100644 --- a/test/Spring/Spring.Data.Tests/Data/Core/TxScopeTransactionManagerTests.cs +++ b/test/Spring/Spring.Data.Tests/Data/Core/TxScopeTransactionManagerTests.cs @@ -68,7 +68,9 @@ namespace Spring.Data.Core } mocks.ReplayAll(); - IPlatformTransactionManager tm = new TxScopeTransactionManager(txAdapter); + TxScopeTransactionManager tm = new TxScopeTransactionManager(txAdapter); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.Execute(delegate(ITransactionStatus status) { @@ -100,7 +102,9 @@ namespace Spring.Data.Core } mocks.ReplayAll(); - IPlatformTransactionManager tm = new TxScopeTransactionManager(txAdapter); + TxScopeTransactionManager tm = new TxScopeTransactionManager(txAdapter); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); Assert.IsTrue(!TransactionSynchronizationManager.SynchronizationActive, "Synchronizations not active"); @@ -155,7 +159,9 @@ namespace Spring.Data.Core } mocks.ReplayAll(); - IPlatformTransactionManager tm = new TxScopeTransactionManager(txAdapter); + TxScopeTransactionManager tm = new TxScopeTransactionManager(txAdapter); + tm.TransactionSynchronization = TransactionSynchronizationState.Always; + TransactionTemplate tt = new TransactionTemplate(tm); tt.PropagationBehavior = TransactionPropagation.RequiresNew; tt.Execute(delegate(ITransactionStatus status) diff --git a/test/Spring/Spring.Data.Tests/Transaction/Support/AbstractPlatformTransactionManagerTests.cs b/test/Spring/Spring.Data.Tests/Transaction/Support/AbstractPlatformTransactionManagerTests.cs index 87c49ab5..d04e30fb 100644 --- a/test/Spring/Spring.Data.Tests/Transaction/Support/AbstractPlatformTransactionManagerTests.cs +++ b/test/Spring/Spring.Data.Tests/Transaction/Support/AbstractPlatformTransactionManagerTests.cs @@ -3,218 +3,219 @@ using NUnit.Framework; namespace Spring.Transaction.Support { - [TestFixture] - public class AbstractPlatformTransactionManagerTests - { - private MockTxnPlatformMgrAbstract _mockTxnMgr; - - [SetUp] - public void Init() - { - _mockTxnMgr = new MockTxnPlatformMgrAbstract(); - if ( TransactionSynchronizationManager.SynchronizationActive ) - { - TransactionSynchronizationManager.ClearSynchronization(); - } - } - [TearDown] - public void Destroy() - { - _mockTxnMgr.Verify(); - _mockTxnMgr = null; - if ( TransactionSynchronizationManager.SynchronizationActive ) - { - TransactionSynchronizationManager.ClearSynchronization(); - } - } - [Test] - public void VanillaProperties() - { - Assert.AreEqual( TransactionSynchronizationState.Always, _mockTxnMgr.TransactionSynchronization); - Assert.IsTrue(!_mockTxnMgr.NestedTransactionsAllowed); - Assert.IsTrue(!_mockTxnMgr.RollbackOnCommitFailure); - - _mockTxnMgr.NestedTransactionsAllowed = true; - _mockTxnMgr.RollbackOnCommitFailure = true; - _mockTxnMgr.TransactionSynchronization = TransactionSynchronizationState.OnActualTransaction; - - Assert.AreEqual( TransactionSynchronizationState.OnActualTransaction, _mockTxnMgr.TransactionSynchronization); - Assert.IsTrue(_mockTxnMgr.NestedTransactionsAllowed); - Assert.IsTrue(_mockTxnMgr.RollbackOnCommitFailure); - } - [Test] - [ExpectedException(typeof(InvalidTimeoutException), ExpectedMessage="Invalid transaction timeout")] - public void DefinitionInvalidTimeoutException() - { - MockTxnDefinition def = new MockTxnDefinition(); - def.TransactionTimeout = -1000; - _mockTxnMgr.GetTransaction( def ); - } - [Test] - [ExpectedException(typeof(IllegalTransactionStateException), ExpectedMessage="Transaction propagation 'mandatory' but no existing transaction found")] - public void DefinitionInvalidPropagationState() - { - MockTxnDefinition def = new MockTxnDefinition(); - def.PropagationBehavior = TransactionPropagation.Mandatory; - _mockTxnMgr.GetTransaction( def ); - } + [TestFixture] + public class AbstractPlatformTransactionManagerTests + { + private MockTxnPlatformMgrAbstract _mockTxnMgr; - [Test] - [ExpectedException(typeof(IllegalTransactionStateException), ExpectedMessage="Transaction propagation 'never' but existing transaction found.")] - public void NeverPropagateState() - { - MockTxnDefinition def = new MockTxnDefinition(); - def.PropagationBehavior = TransactionPropagation.Never; - setGeneralGetTransactionExpectations(); - _mockTxnMgr.GetTransaction(def); - } - [Test] - [ExpectedException(typeof(NestedTransactionNotSupportedException), ExpectedMessage="Transaction manager does not allow nested transactions by default - specify 'NestedTransactionsAllowed' property with value 'true'")] - public void NoNestedTransactionsAllowed() - { - MockTxnDefinition def = new MockTxnDefinition(); - def.PropagationBehavior = TransactionPropagation.Nested; - setGeneralGetTransactionExpectations(); - _mockTxnMgr.GetTransaction(def); - } - [Test] - public void TransactionSuspendedSuccessfully() - { - MockTxnDefinition def = new MockTxnDefinition(); - def.PropagationBehavior = TransactionPropagation.NotSupported; - def.ReadOnly = false; - setGeneralGetTransactionExpectations(); + [SetUp] + public void Init() + { + _mockTxnMgr = new MockTxnPlatformMgrAbstract(); + _mockTxnMgr.TransactionSynchronization = TransactionSynchronizationState.Always; + if (TransactionSynchronizationManager.SynchronizationActive) + { + TransactionSynchronizationManager.ClearSynchronization(); + } + } + [TearDown] + public void Destroy() + { + _mockTxnMgr.Verify(); + _mockTxnMgr = null; + if (TransactionSynchronizationManager.SynchronizationActive) + { + TransactionSynchronizationManager.Clear(); + } + } + [Test] + public void VanillaProperties() + { + Assert.AreEqual(TransactionSynchronizationState.Always, _mockTxnMgr.TransactionSynchronization); + Assert.IsTrue(!_mockTxnMgr.NestedTransactionsAllowed); + Assert.IsTrue(!_mockTxnMgr.RollbackOnCommitFailure); - DefaultTransactionStatus status = (DefaultTransactionStatus)_mockTxnMgr.GetTransaction(def); - Assert.IsNull( status.Transaction ); - Assert.IsTrue( !status.IsNewTransaction ); - Assert.IsTrue( status.NewSynchronization ); - Assert.IsTrue( !status.ReadOnly ); - Assert.IsNotNull( status.SuspendedResources); - } - [Test] - public void TransactionCreatedSuccessfully() - { - MockTxnDefinition def = new MockTxnDefinition(); - def.PropagationBehavior = TransactionPropagation.RequiresNew; - def.ReadOnly = false; + _mockTxnMgr.NestedTransactionsAllowed = true; + _mockTxnMgr.RollbackOnCommitFailure = true; + _mockTxnMgr.TransactionSynchronization = TransactionSynchronizationState.OnActualTransaction; - setGeneralGetTransactionExpectations(); + Assert.AreEqual(TransactionSynchronizationState.OnActualTransaction, _mockTxnMgr.TransactionSynchronization); + Assert.IsTrue(_mockTxnMgr.NestedTransactionsAllowed); + Assert.IsTrue(_mockTxnMgr.RollbackOnCommitFailure); + } + [Test] + [ExpectedException(typeof(InvalidTimeoutException), ExpectedMessage = "Invalid transaction timeout")] + public void DefinitionInvalidTimeoutException() + { + MockTxnDefinition def = new MockTxnDefinition(); + def.TransactionTimeout = -1000; + _mockTxnMgr.GetTransaction(def); + } + [Test] + [ExpectedException(typeof(IllegalTransactionStateException), ExpectedMessage = "Transaction propagation 'mandatory' but no existing transaction found")] + public void DefinitionInvalidPropagationState() + { + MockTxnDefinition def = new MockTxnDefinition(); + def.PropagationBehavior = TransactionPropagation.Mandatory; + _mockTxnMgr.GetTransaction(def); + } - DefaultTransactionStatus status = (DefaultTransactionStatus)_mockTxnMgr.GetTransaction(def); - Assert.AreEqual( _mockTxnMgr.Transaction, status.Transaction ); - Assert.IsTrue( status.IsNewTransaction ); - Assert.IsTrue( status.NewSynchronization ); - Assert.IsTrue( !status.ReadOnly ); - Assert.IsNotNull( status.SuspendedResources); - } - [Test] - public void NestedTransactionSuccessfully() - { - MockTxnDefinition def = new MockTxnDefinition(); - def.PropagationBehavior = TransactionPropagation.Nested; - def.ReadOnly = false; + [Test] + [ExpectedException(typeof(IllegalTransactionStateException), ExpectedMessage = "Transaction propagation 'never' but existing transaction found.")] + public void NeverPropagateState() + { + MockTxnDefinition def = new MockTxnDefinition(); + def.PropagationBehavior = TransactionPropagation.Never; + setGeneralGetTransactionExpectations(); + _mockTxnMgr.GetTransaction(def); + } + [Test] + [ExpectedException(typeof(NestedTransactionNotSupportedException), ExpectedMessage = "Transaction manager does not allow nested transactions by default - specify 'NestedTransactionsAllowed' property with value 'true'")] + public void NoNestedTransactionsAllowed() + { + MockTxnDefinition def = new MockTxnDefinition(); + def.PropagationBehavior = TransactionPropagation.Nested; + setGeneralGetTransactionExpectations(); + _mockTxnMgr.GetTransaction(def); + } + [Test] + public void TransactionSuspendedSuccessfully() + { + MockTxnDefinition def = new MockTxnDefinition(); + def.PropagationBehavior = TransactionPropagation.NotSupported; + def.ReadOnly = false; + setGeneralGetTransactionExpectations(); - setGeneralGetTransactionExpectations(); - _mockTxnMgr.SetExpectedCalls( "DoBegin", 1 ); - _mockTxnMgr.Savepoints = false; - _mockTxnMgr.NestedTransactionsAllowed = true; + DefaultTransactionStatus status = (DefaultTransactionStatus)_mockTxnMgr.GetTransaction(def); + Assert.IsNull(status.Transaction); + Assert.IsTrue(!status.IsNewTransaction); + Assert.IsTrue(status.NewSynchronization); + Assert.IsTrue(!status.ReadOnly); + Assert.IsNotNull(status.SuspendedResources); + } + [Test] + public void TransactionCreatedSuccessfully() + { + MockTxnDefinition def = new MockTxnDefinition(); + def.PropagationBehavior = TransactionPropagation.RequiresNew; + def.ReadOnly = false; - DefaultTransactionStatus status = (DefaultTransactionStatus)_mockTxnMgr.GetTransaction(def); - Assert.AreEqual( _mockTxnMgr.Transaction, status.Transaction ); - Assert.IsTrue( status.IsNewTransaction ); - Assert.AreEqual( true, status.NewSynchronization ); - Assert.IsTrue( !status.ReadOnly ); - Assert.IsNull( status.SuspendedResources); - } + setGeneralGetTransactionExpectations(); - [Test] - public void NestedTransactionWithSavepoint() - { - MockTxnDefinition def = new MockTxnDefinition(); - def.PropagationBehavior = TransactionPropagation.Nested; - def.ReadOnly = false; - setVanillaGetTransactionExpectations(); - _mockTxnMgr.SetTransaction( new MyMockTxnObjectSavepointMgr()); - _mockTxnMgr.SetExpectedCalls("DoBegin", 0); - _mockTxnMgr.Savepoints = true; - _mockTxnMgr.NestedTransactionsAllowed = true; + DefaultTransactionStatus status = (DefaultTransactionStatus)_mockTxnMgr.GetTransaction(def); + Assert.AreEqual(_mockTxnMgr.Transaction, status.Transaction); + Assert.IsTrue(status.IsNewTransaction); + Assert.IsTrue(status.NewSynchronization); + Assert.IsTrue(!status.ReadOnly); + Assert.IsNotNull(status.SuspendedResources); + } + [Test] + public void NestedTransactionSuccessfully() + { + MockTxnDefinition def = new MockTxnDefinition(); + def.PropagationBehavior = TransactionPropagation.Nested; + def.ReadOnly = false; - DefaultTransactionStatus status = (DefaultTransactionStatus)_mockTxnMgr.GetTransaction(def); - Assert.AreEqual( _mockTxnMgr.Transaction, status.Transaction ); - Assert.IsFalse( status.IsNewTransaction ); - Assert.IsFalse( status.NewSynchronization ); - Assert.IsTrue( !status.ReadOnly ); - Assert.IsNull( status.SuspendedResources); - } - [Test] - public void DefaultPropagationBehavior() - { - MockTxnDefinition def = new MockTxnDefinition(); - def.PropagationBehavior = TransactionPropagation.Required; - def.ReadOnly = true; - setGeneralGetTransactionExpectations(); + setGeneralGetTransactionExpectations(); + _mockTxnMgr.SetExpectedCalls("DoBegin", 1); + _mockTxnMgr.Savepoints = false; + _mockTxnMgr.NestedTransactionsAllowed = true; - DefaultTransactionStatus status = (DefaultTransactionStatus)_mockTxnMgr.GetTransaction(def); - Assert.AreEqual( _mockTxnMgr.Transaction, status.Transaction ); - Assert.IsTrue( !status.IsNewTransaction ); - Assert.IsTrue( status.NewSynchronization ); - Assert.IsTrue( status.ReadOnly ); - Assert.IsNull( status.SuspendedResources); - } - [Test] - public void DefaultPropagationBehaviorWithNullDefinition() - { - setGeneralGetTransactionExpectations(); + DefaultTransactionStatus status = (DefaultTransactionStatus)_mockTxnMgr.GetTransaction(def); + Assert.AreEqual(_mockTxnMgr.Transaction, status.Transaction); + Assert.IsTrue(status.IsNewTransaction); + Assert.AreEqual(true, status.NewSynchronization); + Assert.IsTrue(!status.ReadOnly); + Assert.IsNull(status.SuspendedResources); + } - DefaultTransactionStatus status = (DefaultTransactionStatus)_mockTxnMgr.GetTransaction(null); - Assert.AreEqual( _mockTxnMgr.Transaction, status.Transaction ); - Assert.IsTrue( !status.IsNewTransaction ); - Assert.IsTrue( status.NewSynchronization ); - Assert.IsTrue( !status.ReadOnly ); - Assert.IsNull( status.SuspendedResources); - } - [Test] - public void DefaultNoExistingTransaction() - { - setVanillaGetTransactionExpectations(); - _mockTxnMgr.SetExpectedCalls( "DoBegin", 1 ); + [Test] + public void NestedTransactionWithSavepoint() + { + MockTxnDefinition def = new MockTxnDefinition(); + def.PropagationBehavior = TransactionPropagation.Nested; + def.ReadOnly = false; + setVanillaGetTransactionExpectations(); + _mockTxnMgr.SetTransaction(new MyMockTxnObjectSavepointMgr()); + _mockTxnMgr.SetExpectedCalls("DoBegin", 0); + _mockTxnMgr.Savepoints = true; + _mockTxnMgr.NestedTransactionsAllowed = true; - DefaultTransactionStatus status = (DefaultTransactionStatus)_mockTxnMgr.GetTransaction(null); - Assert.IsNotNull( status.Transaction ); - Assert.IsTrue( status.IsNewTransaction ); - Assert.IsTrue( status.NewSynchronization ); - Assert.IsTrue( !status.ReadOnly ); - Assert.IsNull( status.SuspendedResources); - } - [Test] - public void DefaultBehaviorDefaultPropagationNoExistingTransaction() - { - setVanillaGetTransactionExpectations(); - _mockTxnMgr.SetExpectedCalls( "DoBegin", 0 ); - - MockTxnDefinition def = new MockTxnDefinition(); - def.PropagationBehavior = TransactionPropagation.Never; - def.ReadOnly = true; + DefaultTransactionStatus status = (DefaultTransactionStatus)_mockTxnMgr.GetTransaction(def); + Assert.AreEqual(_mockTxnMgr.Transaction, status.Transaction); + Assert.IsFalse(status.IsNewTransaction); + Assert.IsFalse(status.NewSynchronization); + Assert.IsTrue(!status.ReadOnly); + Assert.IsNull(status.SuspendedResources); + } + [Test] + public void DefaultPropagationBehavior() + { + MockTxnDefinition def = new MockTxnDefinition(); + def.PropagationBehavior = TransactionPropagation.Required; + def.ReadOnly = true; + setGeneralGetTransactionExpectations(); - DefaultTransactionStatus status = (DefaultTransactionStatus)_mockTxnMgr.GetTransaction(def); - Assert.IsNull( status.Transaction ); - Assert.IsTrue( !status.IsNewTransaction ); - Assert.IsTrue( status.NewSynchronization ); - Assert.IsTrue( status.ReadOnly ); - Assert.IsNull( status.SuspendedResources); - } - private void setGeneralGetTransactionExpectations() - { - _mockTxnMgr.SetTransaction( new object() ); - setVanillaGetTransactionExpectations(); - } + DefaultTransactionStatus status = (DefaultTransactionStatus)_mockTxnMgr.GetTransaction(def); + Assert.AreEqual(_mockTxnMgr.Transaction, status.Transaction); + Assert.IsTrue(!status.IsNewTransaction); + Assert.IsTrue(status.NewSynchronization); + Assert.IsTrue(status.ReadOnly); + Assert.IsNull(status.SuspendedResources); + } + [Test] + public void DefaultPropagationBehaviorWithNullDefinition() + { + setGeneralGetTransactionExpectations(); - private void setVanillaGetTransactionExpectations() - { - _mockTxnMgr.SetExpectedCalls( "DoGetTransaction", 1); - _mockTxnMgr.SetExpectedCalls( "IsExistingTransaction", 1); - } + DefaultTransactionStatus status = (DefaultTransactionStatus)_mockTxnMgr.GetTransaction(null); + Assert.AreEqual(_mockTxnMgr.Transaction, status.Transaction); + Assert.IsFalse(status.IsNewTransaction); + Assert.IsTrue(status.NewSynchronization); + Assert.IsFalse(status.ReadOnly); + Assert.IsNull(status.SuspendedResources); + } + [Test] + public void DefaultNoExistingTransaction() + { + setVanillaGetTransactionExpectations(); + _mockTxnMgr.SetExpectedCalls("DoBegin", 1); - } + DefaultTransactionStatus status = (DefaultTransactionStatus)_mockTxnMgr.GetTransaction(null); + Assert.IsNotNull(status.Transaction); + Assert.IsTrue(status.IsNewTransaction); + Assert.IsTrue(status.NewSynchronization); + Assert.IsTrue(!status.ReadOnly); + Assert.IsNull(status.SuspendedResources); + } + [Test] + public void DefaultBehaviorDefaultPropagationNoExistingTransaction() + { + setVanillaGetTransactionExpectations(); + _mockTxnMgr.SetExpectedCalls("DoBegin", 0); + + MockTxnDefinition def = new MockTxnDefinition(); + def.PropagationBehavior = TransactionPropagation.Never; + def.ReadOnly = true; + + DefaultTransactionStatus status = (DefaultTransactionStatus)_mockTxnMgr.GetTransaction(def); + Assert.IsNull(status.Transaction); + Assert.IsTrue(!status.IsNewTransaction); + Assert.IsTrue(status.NewSynchronization); + Assert.IsTrue(status.ReadOnly); + Assert.IsNull(status.SuspendedResources); + } + private void setGeneralGetTransactionExpectations() + { + _mockTxnMgr.SetTransaction(new object()); + setVanillaGetTransactionExpectations(); + } + + private void setVanillaGetTransactionExpectations() + { + _mockTxnMgr.SetExpectedCalls("DoGetTransaction", 1); + _mockTxnMgr.SetExpectedCalls("IsExistingTransaction", 1); + } + + } } diff --git a/test/Spring/Spring.Web.Tests/TestSupport/WebApplicationTests.cs b/test/Spring/Spring.Web.Tests/TestSupport/WebApplicationTests.cs index 0c679664..2802a2a7 100644 --- a/test/Spring/Spring.Web.Tests/TestSupport/WebApplicationTests.cs +++ b/test/Spring/Spring.Web.Tests/TestSupport/WebApplicationTests.cs @@ -1,19 +1,19 @@ #region License -/* - * Copyright 2002-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. +/* + * Copyright 2002-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. */ #endregion