From bb7a269789e80e1a9dbd3059bf55e9d38197adda Mon Sep 17 00:00:00 2001 From: markpollack Date: Wed, 16 Jul 2008 18:14:02 +0000 Subject: [PATCH] Add IResourceTransactionManager to indicate which tx managers operate on a since tx resource. Thread local storage slot name for MultiDelegatingDbProvider generated using Spring.Util.UniqueKey. Some simple code cleanup. --- .../Data/Common/DbProviderFactoryObject.cs | 328 ++++++++++-------- .../Data/Common/MultiDelegatingDbProvider.cs | 25 +- .../Core/AdoPlatformTransactionManager.cs | 15 +- .../Spring.Data/Spring.Data.2005.csproj | 1 + .../Interceptor/TransactionAspectSupport.cs | 5 +- .../AbstractPlatformTransactionManager.cs | 36 +- .../Support/IResourceTransactionManager.cs | 49 +++ 7 files changed, 296 insertions(+), 163 deletions(-) create mode 100644 src/Spring/Spring.Data/Transaction/Support/IResourceTransactionManager.cs diff --git a/src/Spring/Spring.Data/Data/Common/DbProviderFactoryObject.cs b/src/Spring/Spring.Data/Data/Common/DbProviderFactoryObject.cs index 38e9ddf5..e6831338 100644 --- a/src/Spring/Spring.Data/Data/Common/DbProviderFactoryObject.cs +++ b/src/Spring/Spring.Data/Data/Common/DbProviderFactoryObject.cs @@ -1,153 +1,177 @@ -#region License - -/* - * Copyright © 2002-2006 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#endregion - -using System; -using Spring.Objects.Factory; -using Spring.Util; - -namespace Spring.Data.Common -{ - /// - /// A implementation that - /// creates instances of the class. - /// - /// Typically used as a convenience for retrieving shared - /// in a Spring XML configuration file as compared to - /// using explict factory method support. - /// - public class DbProviderFactoryObject : IFactoryObject, IInitializingObject - { - private string provider; - private string connectionString; - private IDbProvider dbProvider; - - /// - /// Creates a new instance of the class. - /// - public DbProviderFactoryObject() - { - } - - /// - /// Return an instance of and IDbProvider as configured by this factory - /// managed by this factory. - /// - /// - /// The same (singleton) instance of the IDbProvider managed by - /// this factory. - /// - /// - /// - /// If this method is being called in the context of an enclosing IoC container and - /// returns , the IoC container will consider this factory - /// object as not being fully initialized and throw a corresponding (and most - /// probably fatal) exception. - /// - /// - public object GetObject() - { - lock (this) - { - if (dbProvider == null) - { - ValidateProperties(); - dbProvider = DbProviderFactory.GetDbProvider(Provider); - if (connectionString != null) - { - dbProvider.ConnectionString = this.connectionString; - } - } - - return dbProvider; - } - } - - /// - /// Return the type of - /// - public Type ObjectType - { - get { return typeof (IDbProvider); } - } - - /// - /// Returns true, as the the object managed by this factory is a singleton. - /// - /// - public bool IsSingleton - { - get { return true; } - } - - /// - /// Validates that the provider name is specified. - /// - /// - /// Invoked by an - /// after it has injected all of an object's dependencies. - /// - /// - /// In the event of not setting the ProviderName. - /// - public void AfterPropertiesSet() - { - ValidateProperties(); - } - - /// - /// Gets or sets the name of the database provider. - /// - /// The name of the database provider. - public string Provider - { - get { return provider; } - set - { - AssertUtils.ArgumentHasText(value, "The 'ProviderName' property must have a value."); - provider = value.Trim(); - } - } - - /// - /// Gets or sets the connection string. - /// - /// The connection string. - public string ConnectionString - { - get { return connectionString; } - set - { - AssertUtils.ArgumentHasText(value, "The 'ConnectionString' property must have a value."); - connectionString = value.Trim(); - } - } - - /// - /// Validates the properties. - /// - private void ValidateProperties() - { - if (StringUtils.IsNullOrEmpty(Provider)) - { - throw new ArgumentException("The 'DbProviderName' property has not been set."); - } - } - } +#region License + +/* + * Copyright © 2002-2006 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using Spring.Objects.Factory; +using Spring.Util; + +namespace Spring.Data.Common +{ + /// + /// A implementation that + /// creates instances of the class. + /// + /// Typically used as a convenience for retrieving shared + /// in a Spring XML configuration file as compared to + /// using explict factory method support. + /// + public class DbProviderFactoryObject : IFactoryObject, IInitializingObject + { + #region Fields + + private string provider; + private string connectionString; + private IDbProvider dbProvider; + + #endregion + + #region Constructor + + /// + /// Creates a new instance of the class. + /// + public DbProviderFactoryObject() + { + } + + #endregion + + #region Properties + + /// + /// Gets or sets the name of the database provider. + /// + /// The name of the database provider. + public string Provider + { + get { return provider; } + set + { + AssertUtils.ArgumentHasText(value, "The 'ProviderName' property must have a value."); + provider = value.Trim(); + } + } + + /// + /// Gets or sets the connection string. + /// + /// The connection string. + public string ConnectionString + { + get { return connectionString; } + set + { + AssertUtils.ArgumentHasText(value, "The 'ConnectionString' property must have a value."); + connectionString = value.Trim(); + } + } + + #endregion + + #region IFactoryObject Members + + /// + /// Return an instance of and IDbProvider as configured by this factory + /// managed by this factory. + /// + /// + /// The same (singleton) instance of the IDbProvider managed by + /// this factory. + /// + /// + /// + /// If this method is being called in the context of an enclosing IoC container and + /// returns , the IoC container will consider this factory + /// object as not being fully initialized and throw a corresponding (and most + /// probably fatal) exception. + /// + /// + public object GetObject() + { + lock (this) + { + if (dbProvider == null) + { + ValidateProperties(); + dbProvider = DbProviderFactory.GetDbProvider(Provider); + if (connectionString != null) + { + dbProvider.ConnectionString = this.connectionString; + } + } + + return dbProvider; + } + } + + /// + /// Return the type of + /// + public Type ObjectType + { + get { return typeof (IDbProvider); } + } + + /// + /// Returns true, as the the object managed by this factory is a singleton. + /// + /// + public bool IsSingleton + { + get { return true; } + } + + #endregion + + #region IInitializingObject Memebers + + /// + /// Validates that the provider name is specified. + /// + /// + /// Invoked by an + /// after it has injected all of an object's dependencies. + /// + /// + /// In the event of not setting the ProviderName. + /// + public void AfterPropertiesSet() + { + ValidateProperties(); + } + + #endregion + + #region Private Memebers + + /// + /// Validates the properties. + /// + private void ValidateProperties() + { + if (StringUtils.IsNullOrEmpty(Provider)) + { + throw new ArgumentException("The 'DbProviderName' property has not been set."); + } + } + + #endregion + } } \ No newline at end of file diff --git a/src/Spring/Spring.Data/Data/Common/MultiDelegatingDbProvider.cs b/src/Spring/Spring.Data/Data/Common/MultiDelegatingDbProvider.cs index f5a1c5fb..83620328 100644 --- a/src/Spring/Spring.Data/Data/Common/MultiDelegatingDbProvider.cs +++ b/src/Spring/Spring.Data/Data/Common/MultiDelegatingDbProvider.cs @@ -36,13 +36,15 @@ namespace Spring.Data /// /// /// The name of which DbProvider to use, as provided to the IDictionary property TargetDbProviders - /// is "dbProviderName". Once the target dbprovider name is known, set the name via a call to - /// LogicalThreadContext.SetData("dbProviderName", "database1ProviderName"). The value + /// is "dbProviderName". Once the target dbprovider name is known, set the name via a call to + /// LogicalThreadContext.SetData(MultiDelegatingDbProvider.CURRENT_DBPROVIDER_SLOTNAME, "database1ProviderName"). The value /// "database1ProviderName" must match a key in the provided TargetDbProviders dictionary. /// /// Mark Pollack public class MultiDelegatingDbProvider : IDbProvider, IInitializingObject - { + { + private static readonly string CURRENT_DBPROVIDER_SLOTNAME = Spring.Util.UniqueKey.GetTypeScopedString(typeof(MultiDelegatingDbProvider), "Current"); + private IDictionary targetDbProviders = new SynchronizedHashtable(); #region Constructors @@ -253,14 +255,27 @@ namespace Spring.Data /// /// The corresonding IDbProvider. protected virtual IDbProvider GetTargetProvider() - { - string dbProviderName = (string)LogicalThreadContext.GetData("dbProviderName"); + { + string dbProviderName = (string)LogicalThreadContext.GetData(CURRENT_DBPROVIDER_SLOTNAME); if (targetDbProviders.Contains(dbProviderName)) { return (IDbProvider)targetDbProviders[dbProviderName]; } throw new InvalidDataAccessApiUsageException("'" + dbProviderName + "'" + "was not under the thread local key 'dbProviderName'"); + } + + /// + /// Convenience method to sets the name of the DbProvider that will be used for the + /// the current thread's procesing. + /// + /// The name of the DbProvider to use for the current threads processing. + public static string CurrentDbProviderName + { + set + { + LogicalThreadContext.SetData(CURRENT_DBPROVIDER_SLOTNAME, value); + } } } } diff --git a/src/Spring/Spring.Data/Data/Core/AdoPlatformTransactionManager.cs b/src/Spring/Spring.Data/Data/Core/AdoPlatformTransactionManager.cs index 388290e7..d4b84b9d 100644 --- a/src/Spring/Spring.Data/Data/Core/AdoPlatformTransactionManager.cs +++ b/src/Spring/Spring.Data/Data/Core/AdoPlatformTransactionManager.cs @@ -37,7 +37,7 @@ namespace Spring.Data.Core /// interface. /// /// Mark Pollack (.NET) - public class AdoPlatformTransactionManager : AbstractPlatformTransactionManager, IInitializingObject + public class AdoPlatformTransactionManager : AbstractPlatformTransactionManager, IResourceTransactionManager, IInitializingObject { private IDbProvider dbProvider; @@ -425,6 +425,19 @@ namespace Spring.Data.Core { throw new ArgumentException("DbProvider is required"); } + } + + /// + /// Gets the resource factory that this transaction manager operates on, + /// For the AdoPlatformTransactionManager this is the DbProvider + /// + /// The DbProvider. + public object ResourceFactory + { + get + { + return dbProvider; + } } } } diff --git a/src/Spring/Spring.Data/Spring.Data.2005.csproj b/src/Spring/Spring.Data/Spring.Data.2005.csproj index 06d2bfdc..d205235d 100644 --- a/src/Spring/Spring.Data/Spring.Data.2005.csproj +++ b/src/Spring/Spring.Data/Spring.Data.2005.csproj @@ -257,6 +257,7 @@ + diff --git a/src/Spring/Spring.Data/Transaction/Interceptor/TransactionAspectSupport.cs b/src/Spring/Spring.Data/Transaction/Interceptor/TransactionAspectSupport.cs index e0eab33d..be827cbe 100644 --- a/src/Spring/Spring.Data/Transaction/Interceptor/TransactionAspectSupport.cs +++ b/src/Spring/Spring.Data/Transaction/Interceptor/TransactionAspectSupport.cs @@ -336,8 +336,9 @@ namespace Spring.Transaction.Interceptor /// Transaction Info for declarative transaction management. protected TransactionInfo CreateTransactionIfNecessary(ITransactionAttribute sourceAttr, string joinpointIdentification) { - ITransactionAttribute txAttr = sourceAttr; - + ITransactionAttribute txAttr = sourceAttr; + + // If no name specified, apply method identification as transaction name. if (txAttr != null && txAttr.Name == null) { txAttr = new DelegatingTransactionAttributeWithName(txAttr, joinpointIdentification); diff --git a/src/Spring/Spring.Data/Transaction/Support/AbstractPlatformTransactionManager.cs b/src/Spring/Spring.Data/Transaction/Support/AbstractPlatformTransactionManager.cs index 7acfc1a2..49374a53 100644 --- a/src/Spring/Spring.Data/Transaction/Support/AbstractPlatformTransactionManager.cs +++ b/src/Spring/Spring.Data/Transaction/Support/AbstractPlatformTransactionManager.cs @@ -232,7 +232,7 @@ namespace Spring.Transaction.Support #endregion - #region Abstract Methods + #region Protected Methods /// /// Return the current transaction object. @@ -412,7 +412,36 @@ namespace Spring.Transaction.Support protected virtual bool UseSavepointForNestedTransaction() { return true; - } + } + + /// + /// Register the given list of transaction synchronizations with the existing transaction. + /// + /// + /// Invoked when the control of the Spring transaction manager and thus all Spring + /// transaction synchronizations end, without the transaction being completed yet. This + /// is for example the case when participating in an existing System.Transactions or + /// EnterpriseServices transaction invoked via their APIs. + /// + /// The default implementation simply invokes the AfterCompletion methods + /// immediately, passing in TransactionSynchronizationStatus.Unknown. + /// This is the best we can do if there's no chance to determine the actual + /// outcome of the outer transaction. + /// + /// + /// The transaction transaction object returned by DoGetTransaction. + /// The lList of TransactionSynchronization objects. + /// In case of errors + /// + /// + /// + protected virtual void RegisterAfterCompletionWithExistingTransaction(Object transaction, IList synchronizations) + { + + log.Debug("Cannot register Spring after-completion synchronization with existing transaction - " + + "processing Spring after-completion callbacks immediately, with outcome status 'unknown'"); + InvokeAfterCompletion(synchronizations, TransactionSynchronizationStatus.Unknown); + } /// /// Cleanup resources after transaction completion. @@ -1129,7 +1158,8 @@ namespace Spring.Transaction.Support else { //TODO investigate parallel of JTA/System.Txs - log.Info("Transaction controlled outside of spring tx manager."); + log.Info("Transaction controlled outside of spring tx manager."); + RegisterAfterCompletionWithExistingTransaction(status.Transaction, synchronizations); } } } diff --git a/src/Spring/Spring.Data/Transaction/Support/IResourceTransactionManager.cs b/src/Spring/Spring.Data/Transaction/Support/IResourceTransactionManager.cs new file mode 100644 index 00000000..a04e7176 --- /dev/null +++ b/src/Spring/Spring.Data/Transaction/Support/IResourceTransactionManager.cs @@ -0,0 +1,49 @@ +#region License + +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +namespace Spring.Transaction.Support +{ + /// + /// Extension of the + /// interface, indicating a native resource transaction manager, operating on a single + /// target resource. Such transaction managers differ from DTC based transaction managers in + /// that they do not use transaction enlistment for an open number of resources but + /// rather focus on leveraging the native power and simplicity of a single target resource. + /// + /// + /// This interface is mainly used for abstract introspection of a transaction manager, + /// giving clients a hint on what kind of transaction manager they have been given + /// and on what concrete resource the transaction manager is operating on. + /// + /// Juergen Hoeller + /// Mark Pollack + public interface IResourceTransactionManager : IPlatformTransactionManager + { + /// + /// Gets the resource factory that this transaction manager operates on, + /// e.g. a IDbProvider or a Hibernate ISessionFactory. + /// + /// The resource factory. + object ResourceFactory + { + get; + } + } +} \ No newline at end of file