diff --git a/test/Spring/Spring.SessionFactoryImplError.Tests/CustomSpring132HibernateTxScopeTransactionManager.cs b/test/Spring/Spring.SessionFactoryImplError.Tests/CustomSpring132HibernateTxScopeTransactionManager.cs
new file mode 100644
index 00000000..009f5299
--- /dev/null
+++ b/test/Spring/Spring.SessionFactoryImplError.Tests/CustomSpring132HibernateTxScopeTransactionManager.cs
@@ -0,0 +1,1168 @@
+using System;
+using System.Data;
+using System.Reflection;
+using System.Transactions;
+using NHibernate;
+using NHibernate.Transaction;
+using Spring.Core.TypeResolution;
+using Spring.Dao;
+using Spring.Data.Common;
+using Spring.Data.Core;
+using Spring.Data.NHibernate;
+using Spring.Data.Support;
+using Spring.Objects.Factory;
+using Spring.Transaction;
+using Spring.Transaction.Support;
+using HibernateTransactionException = NHibernate.TransactionException;
+
+namespace Spring.SessionFactoryImplError.Tests
+{
+ ///
+ /// A complete copy and selective re-write of the Spring.Data.NHibernate. from the Spring.Data.NHibernate32 DLL
+ /// to patch bugs with their implementation that could not be rectified by overriding the class (due to so much private scoping).
+ ///
+ ///
+ /// This will be committed to source control in its first version the same way as it appeared in the Spring.NET v1.3.2 (.NET v4.0, VS 2010) source code; therefore, changes
+ /// from the base version in v1.3.2 will be discernable from source control. Those changes will need to be ported to the version of the
+ /// in the next upgrade of Spring.NET, and so forth. A new copy of that versions HibernateTxScopeTransactionManager will need to be copied into a CustomSpring132HibernateTxScopeTransactionManager
+ /// file, that should be committed, then the changes need to be ported. This should continue until Spring's version is bug-free.
+ ///
+ public class CustomSpring132HibernateTxScopeTransactionManager : 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;
+
+ private TxScopeTransactionManager txScopeTranactionManager;
+
+ private static readonly MethodInfo SessionFactoryUtils_CloseSessionOrRegisterDeferredClose_Method;
+
+ #endregion
+
+ #region Constructor (s)
+
+ ///
+ /// Static constructor to get a handle to a internal method from a spring assembly that is needed in this assembly.
+ ///
+ static CustomSpring132HibernateTxScopeTransactionManager()
+ {
+ Type sfuType = typeof (SessionFactoryUtils);
+ SessionFactoryUtils_CloseSessionOrRegisterDeferredClose_Method = sfuType.GetMethod("CloseSessionOrRegisterDeferredClose", BindingFlags.Static | BindingFlags.NonPublic);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CustomSpring132HibernateTxScopeTransactionManager()
+ {
+ txScopeTranactionManager = new TxScopeTransactionManager();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The session factory.
+ public CustomSpring132HibernateTxScopeTransactionManager(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()
+ {
+ if (log.IsDebugEnabled)
+ {
+ if (System.Transactions.Transaction.Current == null)
+ {
+ log.Debug("DoGetTransaction: No transaction currently exists");
+ }
+ else
+ {
+ log.Debug(string.Format("DoGetTransaction: Getting transaction object for existing System.Transactions.Transaction{0}", WriteTransactionInformation(System.Transactions.Transaction.Current)));
+ }
+ }
+
+ 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;
+ }
+ }
+ txObject.PromotableTxScopeTransactionObject = new TxScopeTransactionManager.PromotableTxScopeTransactionObject();
+
+ 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).PromotableTxScopeTransactionObject.TxScopeAdapter.
+ IsExistingTransaction &&
+ ((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)
+ {
+
+ TxScopeTransactionManager.PromotableTxScopeTransactionObject promotableTxScopeTransactionObject =
+ ((HibernateTransactionObject)transaction).PromotableTxScopeTransactionObject;
+ try
+ {
+ DoTxScopeBegin(promotableTxScopeTransactionObject, definition);
+ }
+ catch (Exception e)
+ {
+ throw new CannotCreateTransactionException("Transaction Scope failure on begin", e);
+ }
+
+ if (log.IsDebugEnabled)
+ {
+ log.Debug(string.Format("DoBegin: Began new System.Transactions.Transaction{0}", WriteTransactionInformation(System.Transactions.Transaction.Current)));
+ }
+
+ 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;
+ try
+ {
+ con = session.Connection;
+ //TODO isolation level mgmt
+ //IsolationLevel previousIsolationLevel =
+ }
+ catch (System.Data.SqlClient.SqlException ex)
+ {
+ // Retry logic as a workaround for ADO.NET 2.0 vulnerabilty: KB916002 and MS Connect bug 93731
+ if (ex.Message.Contains("New request is not allowed to start because it should come with valid transaction descriptor"))
+ {
+ if (log.IsWarnEnabled)
+ {
+ log.Warn("Forced to clear all SQL connection pools due to corrupted transaction context on a connection from the connection pool (KB916002).");
+ }
+ System.Data.SqlClient.SqlConnection.ClearAllPools();
+ con = session.Connection;
+ }
+ else
+ {
+ throw;
+ }
+ }
+
+ 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);
+ }
+
+
+ }
+
+ private void DoTxScopeBegin(TxScopeTransactionManager.PromotableTxScopeTransactionObject txObject,
+ Spring.Transaction.ITransactionDefinition definition)
+ {
+
+ TransactionScopeOption txScopeOption = CreateTransactionScopeOptions(definition);
+ TransactionOptions txOptions = CreateTransactionOptions(definition);
+ txObject.TxScopeAdapter.CreateTransactionScope(txScopeOption, txOptions, definition.EnterpriseServicesInteropOption);
+
+ }
+
+ 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;
+ }
+
+
+ private static TransactionOptions CreateTransactionOptions(ITransactionDefinition definition)
+ {
+ TransactionOptions txOptions = new TransactionOptions();
+ switch (definition.TransactionIsolationLevel)
+ {
+ case System.Data.IsolationLevel.Chaos:
+ txOptions.IsolationLevel = System.Transactions.IsolationLevel.Chaos;
+ break;
+ case System.Data.IsolationLevel.ReadCommitted:
+ txOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
+ break;
+ case System.Data.IsolationLevel.ReadUncommitted:
+ txOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted;
+ break;
+ case System.Data.IsolationLevel.RepeatableRead:
+ txOptions.IsolationLevel = System.Transactions.IsolationLevel.RepeatableRead;
+ break;
+ case System.Data.IsolationLevel.Serializable:
+ txOptions.IsolationLevel = System.Transactions.IsolationLevel.Serializable;
+ break;
+ case System.Data.IsolationLevel.Snapshot:
+ txOptions.IsolationLevel = System.Transactions.IsolationLevel.Snapshot;
+ break;
+ case System.Data.IsolationLevel.Unspecified:
+ txOptions.IsolationLevel = System.Transactions.IsolationLevel.Unspecified;
+ break;
+ }
+
+ if (definition.TransactionTimeout != DefaultTransactionDefinition.TIMEOUT_DEFAULT)
+ {
+ txOptions.Timeout = new TimeSpan(0, 0, definition.TransactionTimeout);
+ }
+ return txOptions;
+ }
+
+
+
+ ///
+ /// 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;
+ }
+ finally
+ {
+ DoTxScopeCommit(status);
+ }
+
+
+
+ }
+
+ ///
+ /// Does the tx scope commit.
+ ///
+ /// The status.
+ protected void DoTxScopeCommit(DefaultTransactionStatus status)
+ {
+ TxScopeTransactionManager.PromotableTxScopeTransactionObject txObject =
+ ((HibernateTransactionObject)status.Transaction).PromotableTxScopeTransactionObject;
+ 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);
+ }
+ }
+
+ ///
+ /// 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 (!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 [" +
+ txObject.SessionHolder.Session + "]");
+ }
+ 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)
+ {
+ txObject.SessionHolder.Transaction.Rollback();
+ }
+ else
+ {
+ if (status.Debug)
+ {
+ log.Debug("Unable to RollBack Hibernate transaction; connection for Hibernate transaction on Session [" +
+ txObject.SessionHolder.Session + "] was null");
+ }
+ }
+
+ }
+ 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();
+ }
+ DoTxScopeRollback(status);
+ }*/
+ }
+
+ ///
+ /// Does the tx scope rollback.
+ ///
+ /// The status.
+ protected void DoTxScopeRollback(DefaultTransactionStatus status)
+ {
+ TxScopeTransactionManager.PromotableTxScopeTransactionObject txObject =
+ ((HibernateTransactionObject)status.Transaction).PromotableTxScopeTransactionObject;
+
+ try
+ {
+
+ txObject.TxScopeAdapter.Dispose();
+ }
+ catch (Exception e)
+ {
+ throw new Spring.Transaction.TransactionSystemException("Failure on Transaction Scope rollback.", e);
+ }
+ }
+
+ ///
+ /// 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();
+
+ DoTxScopeSetRollbackOnly(status);
+ }
+
+ ///
+ /// Does the tx scope set rollback only.
+ ///
+ /// The status.
+ protected void DoTxScopeSetRollbackOnly(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);
+ }
+ }
+
+
+ ///
+ /// 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 flushing: " + 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);
+ // NOTE: The above line has to be called via reflection since it is an internal method
+ SessionFactoryUtils_CloseSessionOrRegisterDeferredClose_Method.Invoke(null, BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic, null, new object[] { session, SessionFactory }, null);
+ }
+ 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;
+
+ private TxScopeTransactionManager.PromotableTxScopeTransactionObject promotableTxScopeTransactionObject;
+
+
+ public void SetSessionHolder(SessionHolder sessionHolder, bool newSessionHolder)
+ {
+ this.sessionHolder = sessionHolder;
+ this.newSessionHolder = newSessionHolder;
+ }
+
+ public TxScopeTransactionManager.PromotableTxScopeTransactionObject PromotableTxScopeTransactionObject
+ {
+ get { return promotableTxScopeTransactionObject; }
+ set { this.promotableTxScopeTransactionObject = value; }
+ }
+
+ 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()
+ {
+ if (SessionHolder != null)
+ {
+ 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 != null && 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");
+ }
+
+ }
+ }
+
+ private static string WriteTransactionInformation(System.Transactions.Transaction tx)
+ {
+ if (tx == null)
+ {
+ return "NULL";
+ }
+
+ string isoLevel = System.Transactions.Transaction.Current.IsolationLevel.ToString();
+ TransactionInformation txInfo = System.Transactions.Transaction.Current.TransactionInformation;
+ return string.Format("{{LocalId = {0}, DistributedId = {1}, Status = {2}, IsolationLevel = {3}, CreationTime = {4}}}", txInfo.LocalIdentifier, txInfo.DistributedIdentifier, txInfo.Status, isoLevel, txInfo.CreationTime.ToString("o"));
+ }
+
+ }
+}
diff --git a/test/Spring/Spring.SessionFactoryImplError.Tests/IService1.cs b/test/Spring/Spring.SessionFactoryImplError.Tests/IService1.cs
index 866f13b8..29ce356a 100644
--- a/test/Spring/Spring.SessionFactoryImplError.Tests/IService1.cs
+++ b/test/Spring/Spring.SessionFactoryImplError.Tests/IService1.cs
@@ -2,8 +2,14 @@
{
public interface IService1
{
- void ServiceMethod1();
+ void ServiceMethodWithNotSupported1();
- void ServiceMethod2();
+ void ServiceMethodWithNotSupported2();
+
+ void ServiceMethodWithNotSupported3();
+
+ void ServiceMethodWithNotSupported4();
+
+ void ServiceMethodWithRequired();
}
}
diff --git a/test/Spring/Spring.SessionFactoryImplError.Tests/IService2.cs b/test/Spring/Spring.SessionFactoryImplError.Tests/IService2.cs
index 9031a68f..0b82a843 100644
--- a/test/Spring/Spring.SessionFactoryImplError.Tests/IService2.cs
+++ b/test/Spring/Spring.SessionFactoryImplError.Tests/IService2.cs
@@ -2,8 +2,8 @@
{
public interface IService2
{
- void ServiceMethod1();
+ void ServiceMethodWithNotSupported();
- void ServiceMethod2();
+ void ServiceMethodWithRequiresNew();
}
}
diff --git a/test/Spring/Spring.SessionFactoryImplError.Tests/Service1.cs b/test/Spring/Spring.SessionFactoryImplError.Tests/Service1.cs
index 54be54ce..48642c57 100644
--- a/test/Spring/Spring.SessionFactoryImplError.Tests/Service1.cs
+++ b/test/Spring/Spring.SessionFactoryImplError.Tests/Service1.cs
@@ -13,13 +13,31 @@ namespace Spring.SessionFactoryImplError.Tests
#endregion
[Transaction.Interceptor.Transaction(TransactionPropagation.NotSupported)]
- public virtual void ServiceMethod1()
+ public virtual void ServiceMethodWithNotSupported1()
{
- ServiceMethod2();
+ ServiceMethodWithNotSupported2();
}
[Transaction.Interceptor.Transaction(TransactionPropagation.NotSupported)]
- public virtual void ServiceMethod2()
+ public virtual void ServiceMethodWithNotSupported2()
+ {
+ // do some stuff
+ }
+
+ [Transaction.Interceptor.Transaction(TransactionPropagation.NotSupported)]
+ public virtual void ServiceMethodWithNotSupported3()
+ {
+ ServiceMethodWithNotSupported4();
+ }
+
+ [Transaction.Interceptor.Transaction(TransactionPropagation.NotSupported)]
+ public virtual void ServiceMethodWithNotSupported4()
+ {
+ ServiceMethodWithRequired();
+ }
+
+ [Transaction.Interceptor.Transaction(TransactionPropagation.Required)]
+ public virtual void ServiceMethodWithRequired()
{
// do some stuff
}
diff --git a/test/Spring/Spring.SessionFactoryImplError.Tests/Service2.cs b/test/Spring/Spring.SessionFactoryImplError.Tests/Service2.cs
index 5de7a2cc..32b03572 100644
--- a/test/Spring/Spring.SessionFactoryImplError.Tests/Service2.cs
+++ b/test/Spring/Spring.SessionFactoryImplError.Tests/Service2.cs
@@ -13,13 +13,13 @@ namespace Spring.SessionFactoryImplError.Tests
#endregion
[Transaction.Interceptor.Transaction(TransactionPropagation.NotSupported)]
- public virtual void ServiceMethod1()
+ public virtual void ServiceMethodWithNotSupported()
{
- ServiceMethod2();
+ ServiceMethodWithRequiresNew();
}
[Transaction.Interceptor.Transaction(TransactionPropagation.RequiresNew)]
- public virtual void ServiceMethod2()
+ public virtual void ServiceMethodWithRequiresNew()
{
// do stuff
}
diff --git a/test/Spring/Spring.SessionFactoryImplError.Tests/Spring.Configuration.xml b/test/Spring/Spring.SessionFactoryImplError.Tests/Spring.Configuration.xml
index feac28c9..7334480a 100644
--- a/test/Spring/Spring.SessionFactoryImplError.Tests/Spring.Configuration.xml
+++ b/test/Spring/Spring.SessionFactoryImplError.Tests/Spring.Configuration.xml
@@ -8,11 +8,6 @@
-
-
diff --git a/test/Spring/Spring.SessionFactoryImplError.Tests/Spring.CustomHibernateTxScopeTransactionManager.xml b/test/Spring/Spring.SessionFactoryImplError.Tests/Spring.CustomHibernateTxScopeTransactionManager.xml
new file mode 100644
index 00000000..5657013e
--- /dev/null
+++ b/test/Spring/Spring.SessionFactoryImplError.Tests/Spring.CustomHibernateTxScopeTransactionManager.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
diff --git a/test/Spring/Spring.SessionFactoryImplError.Tests/Spring.HibernateTxScopeTransactionManager.xml b/test/Spring/Spring.SessionFactoryImplError.Tests/Spring.HibernateTxScopeTransactionManager.xml
new file mode 100644
index 00000000..d4d6fa3b
--- /dev/null
+++ b/test/Spring/Spring.SessionFactoryImplError.Tests/Spring.HibernateTxScopeTransactionManager.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
diff --git a/test/Spring/Spring.SessionFactoryImplError.Tests/Spring.SessionFactoryImplError.Tests.csproj b/test/Spring/Spring.SessionFactoryImplError.Tests/Spring.SessionFactoryImplError.Tests.csproj
index 96a02f23..9b6c8909 100644
--- a/test/Spring/Spring.SessionFactoryImplError.Tests/Spring.SessionFactoryImplError.Tests.csproj
+++ b/test/Spring/Spring.SessionFactoryImplError.Tests/Spring.SessionFactoryImplError.Tests.csproj
@@ -47,6 +47,7 @@
+
@@ -55,10 +56,12 @@
+
+
-
+
@@ -95,6 +98,8 @@
+
+