context: using System;
  using System.Collections.Concurrent;
  using System.Collections.Generic;
  using System.Collections.ObjectModel;
  using System.Data.Common;
  using System.Linq;
  using System.Runtime.Serialization;
  using System.Security;
  using System.Text;
  using NHibernate.Cache;
  using NHibernate.Cfg;
  using NHibernate.Connection;
  using NHibernate.Context;
  using NHibernate.Dialect.Function;
  using NHibernate.Engine;
  using NHibernate.Engine.Query;
  using NHibernate.Engine.Query.Sql;
  using NHibernate.Event;
  using NHibernate.Exceptions;
  using NHibernate.Hql;
  using NHibernate.Id;
  using NHibernate.Mapping;
  using NHibernate.Metadata;
  using NHibernate.MultiTenancy;
  using NHibernate.Persister;
  using NHibernate.Persister.Collection;
  using NHibernate.Persister.Entity;
  using NHibernate.Proxy;
  using NHibernate.Stat;
  using NHibernate.Tool.hbm2ddl;
  using NHibernate.Transaction;
  using NHibernate.Type;
  using NHibernate.Util;
  using Environment = NHibernate.Cfg.Environment;
  using HibernateDialect = NHibernate.Dialect.Dialect;
  using IQueryable = NHibernate.Persister.Entity.IQueryable;
  
  namespace NHibernate.Impl
  {
  	/// <summary>
  	///  Concrete implementation of a SessionFactory.
  	/// </summary>
  	/// <remarks>
  	/// Has the following responsibilities:
  	/// <list type="">
  	/// <item>
  	/// Caches configuration settings (immutably)</item>
  	/// <item>
  	/// Caches "compiled" mappings - ie. <see cref="IEntityPersister"/> 
  	/// and <see cref="ICollectionPersister"/>
  	/// </item>
  	/// <item>
  	/// Caches "compiled" queries (memory sensitive cache)
  	/// </item>
  	/// <item>
  	/// Manages <c>PreparedStatements/DbCommands</c> - how true in NH?
  	/// </item>
  	/// <item>
  	/// Delegates <c>DbConnection</c> management to the <see cref="IConnectionProvider"/>
  	/// </item>
  	/// <item>
  	/// Factory for instances of <see cref="ISession"/>
  	/// </item>
  	/// </list>
  	/// <para>
  	/// This class must appear immutable to clients, even if it does all kinds of caching
  	/// and pooling under the covers.  It is crucial that the class is not only thread safe
  	/// , but also highly concurrent.  Synchronization must be used extremely sparingly.
  	/// </para>
  	/// </remarks>
  	/// <seealso cref="IConnectionProvider"/>
  	/// <seealso cref="ISession"/>
  	/// <seealso cref="IQueryTranslator"/>
  	/// <seealso cref="IEntityPersister"/>
  	/// <seealso cref="ICollectionPersister"/>
  	[Serializable]
  	public sealed partial class SessionFactoryImpl : ISessionFactoryImplementor, IObjectReference
  	{
  		#region Default entity not found delegate
  
  		internal class DefaultEntityNotFoundDelegate : IEntityNotFoundDelegate
  		{
  			#region IEntityNotFoundDelegate Members
  
  			public void HandleEntityNotFound(string entityName, object id)
  			{
  				throw new ObjectNotFoundException(id, entityName);
  			}
  
  			public void HandleEntityNotFound(string entityName, string propertyName, object key)
  			{
  				throw new ObjectNotFoundByUniqueKeyException(entityName, propertyName, key);
  			}
  
  			#endregion
  		}
  
  		#endregion
  
  		private static readonly INHibernateLogger log = NHibernateLogger.For(typeof(SessionFactoryImpl));
  		private static readonly IIdentifierGenerator UuidGenerator = new UUIDHexGenerator();
  
  		[NonSerialized]
  		private readonly ConcurrentDictionary<string, CacheBase> _allCacheRegions =
  			new ConcurrentDictionary<string, CacheBase>();
  
  		[NonSerialized]
  		private readonly IDictionary<string, IClassMetadata> classMetadata;
  
  		[NonSerialized]
  		private readonly IDictionary<string, ICollectionMetadata> collectionMetadata;
  		[NonSerialized]
  		private readonly Dictionary<string, ICollectionPersister> collectionPersisters;
  
  		[NonSerialized]
  		private readonly IDictionary<string, ISet<string>> collectionRolesByEntityParticipant;
  		[NonSerialized]
  		private readonly ICurrentSessionContext currentSessionContext;
  		[NonSerialized]
  		private readonly IEntityNotFoundDelegate entityNotFoundDelegate;
  		[NonSerialized]
  		private readonly IDictionary<string, IEntityPersister> entityPersisters;
  
  		/// <summary>
  		/// NH specific : to avoid the use of entityName for generic implementation
  		/// </summary>
  		/// <remarks>this is a shortcut.</remarks>
  		[NonSerialized]
  		private readonly IDictionary<System.Type, string> implementorToEntityName;
  
  		[NonSerialized]
  		private readonly EventListeners eventListeners;
  
  		[NonSerialized]
  		private readonly Dictionary<string, FilterDefinition> filters;
  		[NonSerialized]
  		private readonly Dictionary<string, IIdentifierGenerator> identifierGenerators;
  
  		[NonSerialized]
  		private readonly Dictionary<string, string> imports;
  
  		[NonSerialized]
  		private readonly IInterceptor interceptor;
  		private readonly string name;
  		[NonSerialized]
  		private readonly Dictionary<string, NamedQueryDefinition> namedQueries;
  
  		[NonSerialized]
  		private readonly Dictionary<string, NamedSQLQueryDefinition> namedSqlQueries;
  
  		[NonSerialized]
  		private readonly IDictionary<string, string> properties;
  
  		[NonSerialized]
  		private readonly IQueryCache queryCache;
  
  		[NonSerialized]
  		private readonly ConcurrentDictionary<string, Lazy<IQueryCache>> queryCaches;
  		[NonSerialized]
  		private readonly SchemaExport schemaExport;
  		[NonSerialized]
  		private readonly Settings settings;
  
  		[NonSerialized]
  		private readonly SQLFunctionRegistry sqlFunctionRegistry;
  		[NonSerialized]
  		private readonly Dictionary<string, ResultSetMappingDefinition> sqlResultSetMappings;
  		[NonSerialized]
  		private readonly UpdateTimestampsCache updateTimestampsCache;
  		[NonSerialized]
  		private readonly ConcurrentDictionary<string, string[]> entityNameImplementorsMap = new ConcurrentDictionary<string, string[]>(4 * System.Environment.ProcessorCount, 100);
  		private readonly string uuid;
  		private bool disposed;
  
  		[NonSerialized]
  		private bool isClosed = false;
  
  		private QueryPlanCache queryPlanCache;
  		[NonSerialized]
  		private StatisticsImpl statistics;
  
  		public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings, EventListeners listeners)
  		{
  			this.settings = settings;
  
  			Init();
  			
  			log.Info("building session factory");
  
  			properties = new Dictionary<string, string>(cfg.Properties);
  			interceptor = cfg.Interceptor;
  			
  			sqlFunctionRegistry = new SQLFunctionRegistry(settings.Dialect, cfg.SqlFunctions);
  			eventListeners = listeners;
  			filters = new Dictionary<string, FilterDefinition>(cfg.FilterDefinitions);
  			if (log.IsDebugEnabled())
  			{
  				log.Debug("Session factory constructed with filter configurations : {0}", CollectionPrinter.ToString(filters));
  			}
  
  			if (log.IsDebugEnabled())
  			{
  				log.Debug("instantiating session factory with properties: {0}", CollectionPrinter.ToString(properties));
  			}
  
  			try
  			{
  				if (settings.IsKeywordsImportEnabled)
  				{
  					SchemaMetadataUpdater.Update(this);
  				}
  				if (settings.IsAutoQuoteEnabled)
  				{
  					SchemaMetadataUpdater.QuoteTableAndColumns(cfg, Dialect);
  				}
  			}
  			catch (NotSupportedException ex)
  			{
  				// Ignore if the Dialect does not provide DataBaseSchema
  				log.Warn(ex, "Dialect does not provide DataBaseSchema, but keywords import or auto quoting is enabled.");
  			}
  
  			#region Serialization info
  
  			name = settings.SessionFactoryName;
  			try
  			{
  				uuid = (string)UuidGenerator.Generate(null, null);
  			}
  			catch (Exception ex)
  			{
  				throw new AssertionFailure("Could not generate UUID", ex);
  			}
  
  			SessionFactoryObjectFactory.AddInstance(uuid, name, this, properties);
  
  			#endregion
  
  			#region Caches
  			settings.CacheProvider.Start(properties);
  			#endregion
  
  			#region Generators
  			identifierGenerators = new Dictionary<string, IIdentifierGenerator>();
  			foreach (PersistentClass model in cfg.ClassMappings)
  			{
  				if (!model.IsInherited)
  				{
  					IIdentifierGenerator generator =
  						model.Identifier.CreateIdentifierGenerator(settings.Dialect, settings.DefaultCatalogName,
  																   settings.DefaultSchemaName, (RootClass)model);
  
  					identifierGenerators[model.EntityName] = generator;
  				}
  			}
  			#endregion
  
  			#region Persisters
  
  			var caches = new Dictionary<Tuple<string, string>, ICacheConcurrencyStrategy>();
  			entityPersisters = new Dictionary<string, IEntityPersister>();
  			implementorToEntityName = new Dictionary<System.Type, string>();
  
  			Dictionary<string, IClassMetadata> classMeta = new Dictionary<string, IClassMetadata>();
  
  			foreach (PersistentClass model in cfg.ClassMappings)
  			{
  				model.PrepareTemporaryTables(mapping, settings.Dialect);
  				var cache = GetCacheConcurrencyStrategy(
  					model.RootClazz.CacheRegionName,
  					model.CacheConcurrencyStrategy,
  					model.IsMutable,
  					caches);
  				var cp = PersisterFactory.CreateClassPersister(model, cache, this, mapping);
  				entityPersisters[model.EntityName] = cp;
  				classMeta[model.EntityName] = cp.ClassMetadata;
  
  				if (model.HasPocoRepresentation)
  				{
  					implementorToEntityName[model.MappedClass] = model.EntityName;
  				}
  			}
  			classMetadata = new ReadOnlyDictionary<string, IClassMetadata>(classMeta);
  
  			Dictionary<string, ISet<string>> tmpEntityToCollectionRoleMap = new Dictionary<string, ISet<string>>();
  			collectionPersisters = new Dictionary<string, ICollectionPersister>();
  			foreach (Mapping.Collection model in cfg.CollectionMappings)
  			{
  				var cache = GetCacheConcurrencyStrategy(
  					model.CacheRegionName,
  					model.CacheConcurrencyStrategy,
  					model.Owner.IsMutable,
  					caches);
  				var persister = PersisterFactory.CreateCollectionPersister(model, cache, this);
  				collectionPersisters[model.Role] = persister;
  				IType indexType = persister.IndexType;
  				if (indexType != null && indexType.IsAssociationType && !indexType.IsAnyType)
  				{
  					string entityName = ((IAssociationType)indexType).GetAssociatedEntityName(this);
  					ISet<string> roles;
  					if (!tmpEntityToCollectionRoleMap.TryGetValue(entityName, out roles))
  					{
  						roles = new HashSet<string>();
  						tmpEntityToCollectionRoleMap[entityName] = roles;
  					}
  					roles.Add(persister.Role);
  				}
  				IType elementType = persister.ElementType;
  				if (elementType.IsAssociationType && !elementType.IsAnyType)
  				{
  					string entityName = ((IAssociationType)elementType).GetAssociatedEntityName(this);
  					ISet<string> roles;
  					if (!tmpEntityToCollectionRoleMap.TryGetValue(entityName, out roles))
  					{
  						roles = new HashSet<string>();
  						tmpEntityToCollectionRoleMap[entityName] = roles;
  					}
  					roles.Add(persister.Role);
  				}
  			}
  			Dictionary<string, ICollectionMetadata> tmpcollectionMetadata = new Dictionary<string, ICollectionMetadata>(collectionPersisters.Count);
  			foreach (KeyValuePair<string, ICollectionPersister> collectionPersister in collectionPersisters)
  			{
  				tmpcollectionMetadata.Add(collectionPersister.Key, collectionPersister.Value.CollectionMetadata);
  			}
  			collectionMetadata = new ReadOnlyDictionary<string, ICollectionMetadata>(tmpcollectionMetadata);
  			collectionRolesByEntityParticipant = new ReadOnlyDictionary<string, ISet<string>>(tmpEntityToCollectionRoleMap);
  			#endregion
  
  			#region Named Queries
  			namedQueries = new Dictionary<string, NamedQueryDefinition>(cfg.NamedQueries);
  			namedSqlQueries = new Dictionary<string, NamedSQLQueryDefinition>(cfg.NamedSQLQueries);
  			sqlResultSetMappings = new Dictionary<string, ResultSetMappingDefinition>(cfg.SqlResultSetMappings);
  			#endregion
  
  			imports = new Dictionary<string, string>(cfg.Imports);
  
  			#region after *all* persisters and named queries are registered
  			foreach (IEntityPersister persister in entityPersisters.Values)
  			{
  				persister.PostInstantiate();
  			}
  			foreach (ICollectionPersister persister in collectionPersisters.Values)
  			{
  				persister.PostInstantiate();
  			}
  			#endregion
  
  			log.Debug("Instantiated session factory");
  
  			#region Schema management
  			if (settings.IsAutoCreateSchema)
  			{
  				new SchemaExport(cfg).Create(false, true);
  			}
  
  			if (settings.IsAutoUpdateSchema)
  			{
  				var schemaUpdate = new SchemaUpdate(cfg);
  				schemaUpdate.Execute(false, true);
  				if (settings.ThrowOnSchemaUpdate)
  				{
  					if (schemaUpdate.Exceptions.Any())
  					{
  						throw new AggregateHibernateException(
  							"Schema update has failed, see inner exceptions for details", schemaUpdate.Exceptions);
  					}
  				}
  			}
  			
  			if (settings.IsAutoValidateSchema)
  			{
  				new SchemaValidator(cfg, settings).Validate();
  			}
  			
  			if (settings.IsAutoDropSchema)
  			{
  				schemaExport = new SchemaExport(cfg);
  			}
  			#endregion
  
  			#region Obtaining TransactionManager
  			// not ported yet
  			#endregion
  
  			currentSessionContext = BuildCurrentSessionContext();
  
  			if (settings.IsQueryCacheEnabled)
  			{
  				var updateTimestampsCacheName = typeof(UpdateTimestampsCache).Name;
  				updateTimestampsCache = new UpdateTimestampsCache(GetCache(updateTimestampsCacheName));
  				var queryCacheName = typeof(StandardQueryCache).FullName;
  				queryCache = BuildQueryCache(queryCacheName);
  				queryCaches = new ConcurrentDictionary<string, Lazy<IQueryCache>>();
  				queryCaches.TryAdd(queryCacheName, new Lazy<IQueryCache>(() => queryCache));
  			}
  			else
  			{
  				updateTimestampsCache = null;
  				queryCache = null;
  				queryCaches = null;
  			}
  
  			#region Checking for named queries
  			if (settings.IsNamedQueryStartupCheckingEnabled)
  			{
  				IDictionary<string, HibernateException> errors = CheckNamedQueries();
  				if (errors.Count > 0)
  				{
  					StringBuilder failingQueries = new StringBuilder("Errors in named queries: ");
  					foreach (KeyValuePair<string, HibernateException> pair in errors)
  					{
  						failingQueries.Append('{').Append(pair.Key).Append('}');
  						log.Error(pair.Value, "Error in named query: {0}", pair.Key);
  					}
  					throw new AggregateHibernateException(failingQueries.ToString(), errors.Values);
  				}
  			}
  			#endregion
  
  			Statistics.IsStatisticsEnabled = settings.IsStatisticsEnabled;
  
  			// EntityNotFoundDelegate
  			IEntityNotFoundDelegate enfd = cfg.EntityNotFoundDelegate;
  			if (enfd == null)
  			{
  				enfd = new DefaultEntityNotFoundDelegate();
  			}
  			entityNotFoundDelegate = enfd;
  		}
  
  		private IQueryCache BuildQueryCache(string queryCacheName)
  		{
  			return
  				settings.QueryCacheFactory.GetQueryCache(
  					updateTimestampsCache,
  					properties,
  					GetCache(queryCacheName))
  				// 6.0 TODO: remove the coalesce once IQueryCacheFactory todos are done
  #pragma warning disable 618
  				?? settings.QueryCacheFactory.GetQueryCache(
  #pragma warning restore 618
  					queryCacheName,
  					updateTimestampsCache,
  					settings,
  					properties);
  		}
  
  		private ICacheConcurrencyStrategy GetCacheConcurrencyStrategy(
  			string cacheRegion,
  			string strategy,
  			bool isMutable,
  			Dictionary<Tuple<string, string>, ICacheConcurrencyStrategy> caches)
  		{
  			if (strategy == null || !settings.IsSecondLevelCacheEnabled)
  				return null;
  
  			var cacheKey = new Tuple<string, string>(cacheRegion, strategy);
  			if (caches.TryGetValue(cacheKey, out var cache)) 
  				return cache;
  
  			cache = CacheFactory.CreateCache(strategy, GetCache(cacheRegion));
  			caches.Add(cacheKey, cache);
  			if (isMutable && strategy == CacheFactory.ReadOnly)
  				log.Warn("read-only cache configured for mutable: {0}", name);
  
  			return cache;
  		}
  
  		public EventListeners EventListeners
  		{
  			get { return eventListeners; }
  		}
  
  		#region IObjectReference Members
  
  		[SecurityCritical]
  		public object GetRealObject(StreamingContext context)
  		{
  			// the SessionFactory that was serialized only has values in the properties
  			// "name" and "uuid".  In here convert the serialized SessionFactory into
  			// an instance of the SessionFactory in the current AppDomain.
  			log.Debug("Resolving serialized SessionFactory");
  
  			// look for the instance by uuid - this will work when a SessionFactory
  			// is serialized and deserialized in the same AppDomain.
  			ISessionFactory result = SessionFactoryObjectFactory.GetInstance(uuid);
  			if (result == null)
  			{
  				// if we were deserialized into a different AppDomain, look for an instance with the
  				// same name.
  				result = SessionFactoryObjectFactory.GetNamedInstance(name);
  				if (result == null)
  				{
  					throw new NullReferenceException("Could not find a SessionFactory named " + name + " or identified by uuid " + uuid);
  				}
  				else
  				{
  					log.Debug("resolved SessionFactory by name");
  				}
  			}
  			else
  			{
  				log.Debug("resolved SessionFactory by uuid");
  			}
  
  			return result;
  		}
  
  		#endregion
  
  		#region ISessionFactoryImplementor Members
  
  		public ISessionBuilder WithOptions()
  		{
  			return new SessionBuilderImpl(this);
  		}
  
  		public ISession OpenSession()
  		{
  			return WithOptions().OpenSession();
  		}
  
  		// Obsolete since v5
  		[Obsolete("Please use WithOptions instead.")]
  		public ISession OpenSession(DbConnection connection)
  		{
  			return WithOptions()
  				.Connection(connection)
  				.OpenSession();
  		}
  
  		// Obsolete since v5
  		[Obsolete("Please use WithOptions instead.")]
  		public ISession OpenSession(DbConnection connection, IInterceptor sessionLocalInterceptor)
  		{
  			return WithOptions()
  				.Connection(connection)
  				.Interceptor(sessionLocalInterceptor)
  				.OpenSession();
  		}
  
  		// Obsolete since v5
  
gt: 		[Obsolete("Please use WithOptions instead.")]