diff --git a/Spring.Net.2010.sln b/Spring.Net.2010.sln index 21173b1e..1140a8e8 100644 --- a/Spring.Net.2010.sln +++ b/Spring.Net.2010.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.30723.0 +VisualStudioVersion = 12.0.31101.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F04753EF-7A1B-4837-AB63-8C0821E8155D}" ProjectSection(SolutionItems) = preProject @@ -105,6 +105,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spring.Data.NHibernate4.Tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spring.Data.NHibernate4.Integration.Tests.2010", "test\Spring\Spring.Data.NHibernate4.Integration.Tests\Spring.Data.NHibernate4.Integration.Tests.2010.csproj", "{46F8B5E9-ABCF-4306-BC05-FF05B0FAC151}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spring.SessionFactoryImplError.Tests", "test\Spring\Spring.SessionFactoryImplError.Tests\Spring.SessionFactoryImplError.Tests.csproj", "{DE276F7C-4564-49EC-AABC-B964EC3D1626}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|.NET = Debug|.NET @@ -383,26 +385,6 @@ Global {7321149A-47C6-4FBA-9D1A-26FD6815381B}.Release|Any CPU.Build.0 = Release|Any CPU {7321149A-47C6-4FBA-9D1A-26FD6815381B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {7321149A-47C6-4FBA-9D1A-26FD6815381B}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {9F0F739C-876E-4C4B-AA55-9AC9242C25C8}.Debug|.NET.ActiveCfg = Debug|Any CPU - {9F0F739C-876E-4C4B-AA55-9AC9242C25C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9F0F739C-876E-4C4B-AA55-9AC9242C25C8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9F0F739C-876E-4C4B-AA55-9AC9242C25C8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {9F0F739C-876E-4C4B-AA55-9AC9242C25C8}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {9F0F739C-876E-4C4B-AA55-9AC9242C25C8}.Release|.NET.ActiveCfg = Release|Any CPU - {9F0F739C-876E-4C4B-AA55-9AC9242C25C8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9F0F739C-876E-4C4B-AA55-9AC9242C25C8}.Release|Any CPU.Build.0 = Release|Any CPU - {9F0F739C-876E-4C4B-AA55-9AC9242C25C8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {9F0F739C-876E-4C4B-AA55-9AC9242C25C8}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {C33C6A06-885D-49D2-8029-422D38CBC6D7}.Debug|.NET.ActiveCfg = Debug|Any CPU - {C33C6A06-885D-49D2-8029-422D38CBC6D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C33C6A06-885D-49D2-8029-422D38CBC6D7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C33C6A06-885D-49D2-8029-422D38CBC6D7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {C33C6A06-885D-49D2-8029-422D38CBC6D7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {C33C6A06-885D-49D2-8029-422D38CBC6D7}.Release|.NET.ActiveCfg = Release|Any CPU - {C33C6A06-885D-49D2-8029-422D38CBC6D7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C33C6A06-885D-49D2-8029-422D38CBC6D7}.Release|Any CPU.Build.0 = Release|Any CPU - {C33C6A06-885D-49D2-8029-422D38CBC6D7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {C33C6A06-885D-49D2-8029-422D38CBC6D7}.Release|Mixed Platforms.Build.0 = Release|Any CPU {D546EFB7-9F6C-4C11-B2F8-B85FAD135399}.Debug|.NET.ActiveCfg = Debug|Any CPU {D546EFB7-9F6C-4C11-B2F8-B85FAD135399}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D546EFB7-9F6C-4C11-B2F8-B85FAD135399}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -475,16 +457,6 @@ Global {FA934E92-C8C2-428A-BE2A-26818F17A787}.Release|.NET.ActiveCfg = Release|Any CPU {FA934E92-C8C2-428A-BE2A-26818F17A787}.Release|Any CPU.ActiveCfg = Release|Any CPU {FA934E92-C8C2-428A-BE2A-26818F17A787}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {CD6A49DE-745C-4360-AADF-3539CFEC6639}.Debug|.NET.ActiveCfg = Debug|Any CPU - {CD6A49DE-745C-4360-AADF-3539CFEC6639}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD6A49DE-745C-4360-AADF-3539CFEC6639}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CD6A49DE-745C-4360-AADF-3539CFEC6639}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {CD6A49DE-745C-4360-AADF-3539CFEC6639}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {CD6A49DE-745C-4360-AADF-3539CFEC6639}.Release|.NET.ActiveCfg = Release|Any CPU - {CD6A49DE-745C-4360-AADF-3539CFEC6639}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CD6A49DE-745C-4360-AADF-3539CFEC6639}.Release|Any CPU.Build.0 = Release|Any CPU - {CD6A49DE-745C-4360-AADF-3539CFEC6639}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {CD6A49DE-745C-4360-AADF-3539CFEC6639}.Release|Mixed Platforms.Build.0 = Release|Any CPU {6B7DA759-F46D-4860-AF03-E7AC7498E003}.Debug|.NET.ActiveCfg = Debug|Any CPU {6B7DA759-F46D-4860-AF03-E7AC7498E003}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6B7DA759-F46D-4860-AF03-E7AC7498E003}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -515,16 +487,6 @@ Global {8CF0F34A-CC93-4D87-AE14-A2DEEF072F26}.Release|Any CPU.Build.0 = Release|Any CPU {8CF0F34A-CC93-4D87-AE14-A2DEEF072F26}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {8CF0F34A-CC93-4D87-AE14-A2DEEF072F26}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {64400FF8-2E9F-4809-B5F4-0C7EB8ABFF87}.Debug|.NET.ActiveCfg = Debug|Any CPU - {64400FF8-2E9F-4809-B5F4-0C7EB8ABFF87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {64400FF8-2E9F-4809-B5F4-0C7EB8ABFF87}.Debug|Any CPU.Build.0 = Debug|Any CPU - {64400FF8-2E9F-4809-B5F4-0C7EB8ABFF87}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {64400FF8-2E9F-4809-B5F4-0C7EB8ABFF87}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {64400FF8-2E9F-4809-B5F4-0C7EB8ABFF87}.Release|.NET.ActiveCfg = Release|Any CPU - {64400FF8-2E9F-4809-B5F4-0C7EB8ABFF87}.Release|Any CPU.ActiveCfg = Release|Any CPU - {64400FF8-2E9F-4809-B5F4-0C7EB8ABFF87}.Release|Any CPU.Build.0 = Release|Any CPU - {64400FF8-2E9F-4809-B5F4-0C7EB8ABFF87}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {64400FF8-2E9F-4809-B5F4-0C7EB8ABFF87}.Release|Mixed Platforms.Build.0 = Release|Any CPU {CF375928-B6D5-485C-B04D-2BC41D9DBF1E}.Debug|.NET.ActiveCfg = Debug|Any CPU {CF375928-B6D5-485C-B04D-2BC41D9DBF1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CF375928-B6D5-485C-B04D-2BC41D9DBF1E}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -535,16 +497,6 @@ Global {CF375928-B6D5-485C-B04D-2BC41D9DBF1E}.Release|Any CPU.Build.0 = Release|Any CPU {CF375928-B6D5-485C-B04D-2BC41D9DBF1E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {CF375928-B6D5-485C-B04D-2BC41D9DBF1E}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {2FB16852-22AD-4A5B-885A-97136265CC46}.Debug|.NET.ActiveCfg = Debug|Any CPU - {2FB16852-22AD-4A5B-885A-97136265CC46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2FB16852-22AD-4A5B-885A-97136265CC46}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2FB16852-22AD-4A5B-885A-97136265CC46}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {2FB16852-22AD-4A5B-885A-97136265CC46}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {2FB16852-22AD-4A5B-885A-97136265CC46}.Release|.NET.ActiveCfg = Release|Any CPU - {2FB16852-22AD-4A5B-885A-97136265CC46}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2FB16852-22AD-4A5B-885A-97136265CC46}.Release|Any CPU.Build.0 = Release|Any CPU - {2FB16852-22AD-4A5B-885A-97136265CC46}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {2FB16852-22AD-4A5B-885A-97136265CC46}.Release|Mixed Platforms.Build.0 = Release|Any CPU {C57B05EA-FD1A-40EC-BB60-D2E45AB1A86A}.Debug|.NET.ActiveCfg = Debug|Any CPU {C57B05EA-FD1A-40EC-BB60-D2E45AB1A86A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C57B05EA-FD1A-40EC-BB60-D2E45AB1A86A}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -605,6 +557,16 @@ Global {46F8B5E9-ABCF-4306-BC05-FF05B0FAC151}.Release|Any CPU.Build.0 = Release|Any CPU {46F8B5E9-ABCF-4306-BC05-FF05B0FAC151}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {46F8B5E9-ABCF-4306-BC05-FF05B0FAC151}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {DE276F7C-4564-49EC-AABC-B964EC3D1626}.Debug|.NET.ActiveCfg = Debug|Any CPU + {DE276F7C-4564-49EC-AABC-B964EC3D1626}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE276F7C-4564-49EC-AABC-B964EC3D1626}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE276F7C-4564-49EC-AABC-B964EC3D1626}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {DE276F7C-4564-49EC-AABC-B964EC3D1626}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {DE276F7C-4564-49EC-AABC-B964EC3D1626}.Release|.NET.ActiveCfg = Release|Any CPU + {DE276F7C-4564-49EC-AABC-B964EC3D1626}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE276F7C-4564-49EC-AABC-B964EC3D1626}.Release|Any CPU.Build.0 = Release|Any CPU + {DE276F7C-4564-49EC-AABC-B964EC3D1626}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {DE276F7C-4564-49EC-AABC-B964EC3D1626}.Release|Mixed Platforms.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Spring/Spring.Data.NHibernate/Data/NHibernate/HibernateTxScopeTransactionManager.cs b/src/Spring/Spring.Data.NHibernate/Data/NHibernate/HibernateTxScopeTransactionManager.cs index 9342eee6..fa51c74e 100644 --- a/src/Spring/Spring.Data.NHibernate/Data/NHibernate/HibernateTxScopeTransactionManager.cs +++ b/src/Spring/Spring.Data.NHibernate/Data/NHibernate/HibernateTxScopeTransactionManager.cs @@ -342,10 +342,12 @@ namespace Spring.Data.NHibernate /// protected override bool IsExistingTransaction(object transaction) { - return - ((HibernateTransactionObject)transaction).PromotableTxScopeTransactionObject.TxScopeAdapter. - IsExistingTransaction; - //return ((HibernateTransactionObject) transaction).HasTransaction(); + var hibernateTransactionObject = ((HibernateTransactionObject) transaction); + + var hasExistingPromotableTxScopeTransaction = hibernateTransactionObject.PromotableTxScopeTransactionObject.TxScopeAdapter.IsExistingTransaction; + var hasExistingTransaction = hibernateTransactionObject.HasTransaction(); + + return hasExistingPromotableTxScopeTransaction && hasExistingTransaction; } /// @@ -951,12 +953,14 @@ namespace Spring.Data.NHibernate } */ ISession session = txObject.SessionHolder.Session; + if (txObject.NewSessionHolder) { if (log.IsDebugEnabled) { log.Debug("Closing Hibernate Session [" + session + "] after transaction"); } + SessionFactoryUtils.CloseSessionOrRegisterDeferredClose(session, SessionFactory); } else @@ -970,9 +974,8 @@ namespace Spring.Data.NHibernate session.FlushMode = txObject.SessionHolder.PreviousFlushMode; } } + txObject.SessionHolder.Clear(); - - } private class HibernateTransactionObject : AdoTransactionObjectSupport diff --git a/test/Spring/Spring.SessionFactoryImplError.Tests/App.config b/test/Spring/Spring.SessionFactoryImplError.Tests/App.config new file mode 100644 index 00000000..d0476f1b --- /dev/null +++ b/test/Spring/Spring.SessionFactoryImplError.Tests/App.config @@ -0,0 +1,64 @@ + + + + +
+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Spring/Spring.SessionFactoryImplError.Tests/Container.cs b/test/Spring/Spring.SessionFactoryImplError.Tests/Container.cs new file mode 100644 index 00000000..c4f06edf --- /dev/null +++ b/test/Spring/Spring.SessionFactoryImplError.Tests/Container.cs @@ -0,0 +1,10 @@ +using System; + +namespace Spring.SessionFactoryImplError.Tests +{ + public class Container : IContainer + { + public Guid Id { get; set; } + public string Name { get; set; } + } +} diff --git a/test/Spring/Spring.SessionFactoryImplError.Tests/CreateTable.sql b/test/Spring/Spring.SessionFactoryImplError.Tests/CreateTable.sql new file mode 100644 index 00000000..1c96dcf6 --- /dev/null +++ b/test/Spring/Spring.SessionFactoryImplError.Tests/CreateTable.sql @@ -0,0 +1,10 @@ +/* + Script used to create table SQL Server +*/ + +CREATE TABLE [dbo].[Container]( + [Id] [uniqueidentifier] NOT NULL, + [Name] [nvarchar](50) NOT NULL +) ON [PRIMARY] + + 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/IContainer.cs b/test/Spring/Spring.SessionFactoryImplError.Tests/IContainer.cs new file mode 100644 index 00000000..c703a969 --- /dev/null +++ b/test/Spring/Spring.SessionFactoryImplError.Tests/IContainer.cs @@ -0,0 +1,10 @@ +using System; + +namespace Spring.SessionFactoryImplError.Tests +{ + public interface IContainer + { + Guid Id { get; set; } + string Name { get; set; } + } +} diff --git a/test/Spring/Spring.SessionFactoryImplError.Tests/IService1.cs b/test/Spring/Spring.SessionFactoryImplError.Tests/IService1.cs new file mode 100644 index 00000000..29ce356a --- /dev/null +++ b/test/Spring/Spring.SessionFactoryImplError.Tests/IService1.cs @@ -0,0 +1,15 @@ +namespace Spring.SessionFactoryImplError.Tests +{ + public interface IService1 + { + void ServiceMethodWithNotSupported1(); + + 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 new file mode 100644 index 00000000..0b82a843 --- /dev/null +++ b/test/Spring/Spring.SessionFactoryImplError.Tests/IService2.cs @@ -0,0 +1,9 @@ +namespace Spring.SessionFactoryImplError.Tests +{ + public interface IService2 + { + void ServiceMethodWithNotSupported(); + + void ServiceMethodWithRequiresNew(); + } +} diff --git a/test/Spring/Spring.SessionFactoryImplError.Tests/Mappings.hbm.xml b/test/Spring/Spring.SessionFactoryImplError.Tests/Mappings.hbm.xml new file mode 100644 index 00000000..0dc5b421 --- /dev/null +++ b/test/Spring/Spring.SessionFactoryImplError.Tests/Mappings.hbm.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Spring/Spring.SessionFactoryImplError.Tests/Properties/AssemblyInfo.cs b/test/Spring/Spring.SessionFactoryImplError.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..264dec25 --- /dev/null +++ b/test/Spring/Spring.SessionFactoryImplError.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Spring.SessionFactoryImplError.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Spring.SessionFactoryImplError.Tests")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("3f4ca2dc-e2e6-40cc-90d3-0fdda9912e50")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/test/Spring/Spring.SessionFactoryImplError.Tests/Service1.cs b/test/Spring/Spring.SessionFactoryImplError.Tests/Service1.cs new file mode 100644 index 00000000..48642c57 --- /dev/null +++ b/test/Spring/Spring.SessionFactoryImplError.Tests/Service1.cs @@ -0,0 +1,45 @@ +using Spring.Data.NHibernate.Generic; +using Spring.Transaction; + +namespace Spring.SessionFactoryImplError.Tests +{ + [Transaction.Interceptor.Transaction(TransactionPropagation.Supports, ReadOnly = true)] + public class Service1 : IService1 + { + #region DI + + public HibernateTemplate HibernateTemplate { get; set; } + + #endregion + + [Transaction.Interceptor.Transaction(TransactionPropagation.NotSupported)] + public virtual void ServiceMethodWithNotSupported1() + { + ServiceMethodWithNotSupported2(); + } + + [Transaction.Interceptor.Transaction(TransactionPropagation.NotSupported)] + 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 new file mode 100644 index 00000000..32b03572 --- /dev/null +++ b/test/Spring/Spring.SessionFactoryImplError.Tests/Service2.cs @@ -0,0 +1,27 @@ +using Spring.Data.NHibernate.Generic; +using Spring.Transaction; + +namespace Spring.SessionFactoryImplError.Tests +{ + [Transaction.Interceptor.Transaction(TransactionPropagation.Supports, ReadOnly = true)] + public class Service2 : IService2 + { + #region DI + + public HibernateTemplate HibernateTemplate { get; set; } + + #endregion + + [Transaction.Interceptor.Transaction(TransactionPropagation.NotSupported)] + public virtual void ServiceMethodWithNotSupported() + { + ServiceMethodWithRequiresNew(); + } + + [Transaction.Interceptor.Transaction(TransactionPropagation.RequiresNew)] + 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 new file mode 100644 index 00000000..7334480a --- /dev/null +++ b/test/Spring/Spring.SessionFactoryImplError.Tests/Spring.Configuration.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + *Service* + + + + + transactionAdvice + + + + + + + + + + + + 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.NHibernate.xml b/test/Spring/Spring.SessionFactoryImplError.Tests/Spring.NHibernate.xml new file mode 100644 index 00000000..bf72be9c --- /dev/null +++ b/test/Spring/Spring.SessionFactoryImplError.Tests/Spring.NHibernate.xml @@ -0,0 +1,41 @@ + + + Dependency Injection for NHibernate Session + + + + + + + + + + + + + + + + + + + + + + + + + + + assembly://Spring.SessionFactoryImplError.Tests/Spring.SessionFactoryImplError.Tests/Mappings.hbm.xml + + + + + + + + + + + \ No newline at end of file diff --git a/test/Spring/Spring.SessionFactoryImplError.Tests/Spring.SessionFactoryImplError.Tests.csproj b/test/Spring/Spring.SessionFactoryImplError.Tests/Spring.SessionFactoryImplError.Tests.csproj new file mode 100644 index 00000000..9b6c8909 --- /dev/null +++ b/test/Spring/Spring.SessionFactoryImplError.Tests/Spring.SessionFactoryImplError.Tests.csproj @@ -0,0 +1,112 @@ + + + + + Debug + AnyCPU + {DE276F7C-4564-49EC-AABC-B964EC3D1626} + Library + Properties + Spring.SessionFactoryImplError.Tests + Spring.SessionFactoryImplError.Tests + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\..\..\packages\Common.Logging.3.0.0\lib\net40\Common.Logging.dll + + + False + ..\..\..\packages\Common.Logging.Core.3.0.0\lib\net40\Common.Logging.Core.dll + + + ..\..\..\packages\NHibernate.4.0.3.4000\lib\net40\NHibernate.dll + + + False + ..\..\..\packages\NUnit.2.6.3\lib\nunit.framework.dll + + + + + + + + + + + + + + + + + + + + + + + + + {3a3a4e65-45a6-4b20-b460-0bedc302c02c} + Spring.Aop.2010 + + + {710961A3-0DF4-49E4-A26E-F5B9C044AC84} + Spring.Core.2010 + + + {d9c45177-0d80-4133-ab46-58f142db92aa} + Spring.Data.NHibernate4.2010 + + + {ae00e5ab-c39a-436f-86d2-33bfe33e2e40} + Spring.Data.2010 + + + {ed204a7b-832f-44c7-bfe3-504aebe1bcc8} + Spring.Testing.NUnit.2010 + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Spring/Spring.SessionFactoryImplError.Tests/TestUsingCustomHibernateTxScopeTransactionManager.cs b/test/Spring/Spring.SessionFactoryImplError.Tests/TestUsingCustomHibernateTxScopeTransactionManager.cs new file mode 100644 index 00000000..9766825e --- /dev/null +++ b/test/Spring/Spring.SessionFactoryImplError.Tests/TestUsingCustomHibernateTxScopeTransactionManager.cs @@ -0,0 +1,50 @@ +using NUnit.Framework; +using Spring.Data.NHibernate.Generic; +using Spring.Testing.NUnit; + +namespace Spring.SessionFactoryImplError.Tests +{ + [TestFixture] + public class TestUsingCustomHibernateTxScopeTransactionManager : AbstractTransactionalSpringContextTests + { + #region DI + + public HibernateTemplate HibernateTemplate { get; set; } + public IService1 Service1 { get; set; } + public IService2 Service2 { get; set; } + + #endregion + + protected override string[] ConfigLocations + { + get + { + return new[] + { + "config://spring/objects", + "assembly://Spring.SessionFactoryImplError.Tests/Spring.SessionFactoryImplError.Tests/Spring.Configuration.xml", + "assembly://Spring.SessionFactoryImplError.Tests/Spring.SessionFactoryImplError.Tests/Spring.NHibernate.xml", + "assembly://Spring.SessionFactoryImplError.Tests/Spring.SessionFactoryImplError.Tests/Spring.CustomHibernateTxScopeTransactionManager.xml" + }; + } + } + + [Test(Description = "This is failing test case in my unit test with original NH TX Scope Manager, but not Custom TX Scope Manager, Not Supported -> Not Supported")] + public void TestSuspendTransactionOnNotSupported() + { + Service1.ServiceMethodWithNotSupported1(); + } + + [Test(Description = "This is also failing with NH TX Scope manager, but nto with Custom TX Scope Manager, Not Supported -> Requires New")] + public void TestSuspendTransactionOnNotSupportedWithNestedRequiresNew() + { + Service2.ServiceMethodWithNotSupported(); + } + + [Test(Description = "This is failing test case in my unit test with Custom TX Scope Manager (this is the original bug reported), Not Supported -> Not Supported -> Required")] + public void TestSuspendTransactionOnNotSupportedWithNestedRequired() + { + Service1.ServiceMethodWithNotSupported3(); + } + } +} diff --git a/test/Spring/Spring.SessionFactoryImplError.Tests/TestUsingHibernateTxScopeTransactionManager.cs b/test/Spring/Spring.SessionFactoryImplError.Tests/TestUsingHibernateTxScopeTransactionManager.cs new file mode 100644 index 00000000..750762f4 --- /dev/null +++ b/test/Spring/Spring.SessionFactoryImplError.Tests/TestUsingHibernateTxScopeTransactionManager.cs @@ -0,0 +1,51 @@ +using NUnit.Framework; + +using Spring.Data.NHibernate.Generic; +using Spring.Testing.NUnit; + +namespace Spring.SessionFactoryImplError.Tests +{ + [TestFixture] + public class TestUsingHibernateTxScopeTransactionManager : AbstractTransactionalSpringContextTests + { + #region DI + + public HibernateTemplate HibernateTemplate { get; set; } + public IService1 Service1 { get; set; } + public IService2 Service2 { get; set; } + + #endregion + + protected override string[] ConfigLocations + { + get + { + return new[] + { + "config://spring/objects", + "assembly://Spring.SessionFactoryImplError.Tests/Spring.SessionFactoryImplError.Tests/Spring.Configuration.xml", + "assembly://Spring.SessionFactoryImplError.Tests/Spring.SessionFactoryImplError.Tests/Spring.NHibernate.xml", + "assembly://Spring.SessionFactoryImplError.Tests/Spring.SessionFactoryImplError.Tests/Spring.HibernateTxScopeTransactionManager.xml" + }; + } + } + + [Test(Description = "This is failing test case in my unit test with original NH TX Scope Manager, but not Custom TX Scope Manager, Not Supported -> Not Supported")] + public void TestSuspendTransactionOnNotSupported() + { + Service1.ServiceMethodWithNotSupported1(); + } + + [Test(Description = "This is also failing with NH TX Scope manager, but not with Custom TX Scope Manager, Not Supported -> Requires New")] + public void TestSuspendTransactionOnNotSupportedWithNestedRequiresNew() + { + Service2.ServiceMethodWithNotSupported(); + } + + [Test(Description = "This is failing test case in my unit test with Custom TX Scope Manager (this is the original bug reported), Not Supported -> Not Supported -> Required")] + public void TestSuspendTransactionOnNotSupportedWithNestedRequired() + { + Service1.ServiceMethodWithNotSupported3(); + } + } +}