Removed untyped contructor from ComponentRegistration and add a protected setter.
[castle.git] / ActiveRecord / Castle.ActiveRecord / Framework / ActiveRecordStarter.cs
blobc930db93f01c5f77127a65623c3e762a3d074f6c
1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle.ActiveRecord
17 using System;
18 using System.Collections.Generic;
19 using System.Data;
20 using System.IO;
21 using System.Reflection;
22 using Castle.ActiveRecord.Framework;
23 using Castle.ActiveRecord.Framework.Config;
24 using Castle.ActiveRecord.Framework.Internal;
25 using Castle.ActiveRecord.Framework.Scopes;
26 using Castle.Core.Configuration;
27 using NHibernate.Cfg;
28 using Iesi.Collections;
29 using NHibernate.Expressions;
30 using NHibernate.Tool.hbm2ddl;
31 using Environment=NHibernate.Cfg.Environment;
33 /// <summary>
34 /// Delegate for use in <see cref="ActiveRecordStarter.ModelsCreated"/> and <see cref="ActiveRecordStarter.ModelsValidated"/>
35 /// </summary>
36 /// <param name="holder"></param>
37 public delegate void SessionFactoryHolderDelegate(ISessionFactoryHolder holder);
39 /// <summary>
40 /// Delegate for use in <see cref="ActiveRecordStarter.ModelsCreated"/> and <see cref="ActiveRecordStarter.ModelsValidated"/>
41 /// </summary>
42 public delegate void ModelsDelegate(ActiveRecordModelCollection models, IConfigurationSource source);
44 /// <summary>
45 /// Delegate for use in <see cref="ActiveRecordStarter.ModelCreated"/>
46 /// </summary>
47 public delegate void ModelDelegate(ActiveRecordModel model, IConfigurationSource source);
49 /// <summary>
50 /// Performs the framework initialization.
51 /// </summary>
52 /// <remarks>
53 /// This class is not thread safe.
54 /// </remarks>
55 public sealed class ActiveRecordStarter
57 private static readonly Object lockConfig = new object();
59 private static bool isInitialized = false;
61 private static IDictionary<Type, string> registeredTypes;
63 /// <summary>
64 /// This is saved so one can invoke <c>RegisterTypes</c> later
65 /// </summary>
66 private static IConfigurationSource configSource;
68 /// <summary>
69 /// So others frameworks can intercept the
70 /// creation and act on the holder instance
71 /// </summary>
72 public static event SessionFactoryHolderDelegate SessionFactoryHolderCreated;
74 /// <summary>
75 /// Allows other frameworks to modify the ActiveRecordModel
76 /// before the generation of the NHibernate XML configuration.
77 /// As an example, this may be used to rewrite table names to
78 /// conform to an application-specific standard. Since the
79 /// configuration source is passed in, it is possible to
80 /// determine the underlying database type and make changes
81 /// if necessary.
82 /// </summary>
83 public static event ModelsDelegate ModelsCreated;
85 /// <summary>
86 /// Allows other frameworks to modify the ActiveRecordModel
87 /// before the generation of the NHibernate XML configuration.
88 /// As an example, this may be used to rewrite table names to
89 /// conform to an application-specific standard. Since the
90 /// configuration source is passed in, it is possible to
91 /// determine the underlying database type and make changes
92 /// if necessary.
93 /// </summary>
94 public static event ModelsDelegate ModelsValidated;
96 /// <summary>
97 /// Allows other frameworks to modify the ActiveRecordModel
98 /// before the generation of the NHibernate XML configuration.
99 /// </summary>
100 public static event ModelDelegate ModelCreated;
101 // public static event ModelsDelegate ModelsCreated;
104 /// <summary>
105 /// Initialize the mappings using the configuration and
106 /// the list of types
107 /// </summary>
108 public static void Initialize(IConfigurationSource source, params Type[] types)
110 lock(lockConfig)
112 if (isInitialized)
114 throw new ActiveRecordInitializationException("You can't invoke ActiveRecordStarter.Initialize more than once");
117 if (source == null)
119 throw new ArgumentNullException("source");
121 if (types == null)
123 throw new ArgumentNullException("types");
126 registeredTypes = new Dictionary<Type, string>();
128 configSource = source;
130 // First initialization
131 ISessionFactoryHolder holder = CreateSessionFactoryHolderImplementation(source);
133 holder.ThreadScopeInfo = CreateThreadScopeInfoImplementation(source);
135 RaiseSessionFactoryHolderCreated(holder);
137 ActiveRecordBase.holder = holder;
138 ActiveRecordModel.type2Model.Clear();
139 ActiveRecordModel.isDebug = source.Debug;
140 ActiveRecordModel.isLazyByDefault = source.IsLazyByDefault;
141 ActiveRecordModel.pluralizeTableNames = source.PluralizeTableNames;
143 // Sets up base configuration
144 SetUpConfiguration(source, typeof(ActiveRecordBase), holder);
146 RegisterTypes(holder, source, types, true);
148 isInitialized = true;
152 /// <summary>
153 /// Initialize the mappings using the configuration and
154 /// checking all the types on the specified <c>Assembly</c>
155 /// </summary>
156 public static void Initialize(Assembly assembly, IConfigurationSource source)
158 List<Type> list = new List<Type>();
160 CollectValidActiveRecordTypesFromAssembly(assembly, list, source);
162 Initialize(source, list.ToArray());
165 /// <summary>
166 /// Initialize the mappings using the configuration and
167 /// checking all the types on the specified Assemblies
168 /// </summary>
169 public static void Initialize(Assembly[] assemblies, IConfigurationSource source)
171 List<Type> list = new List<Type>();
173 foreach(Assembly assembly in assemblies)
175 CollectValidActiveRecordTypesFromAssembly(assembly, list, source);
178 Initialize(source, list.ToArray());
181 /// <summary>
182 /// Initializes the framework reading the configuration from
183 /// the <c>AppDomain</c> and checking all the types on the executing <c>Assembly</c>
184 /// </summary>
185 public static void Initialize()
187 IConfigurationSource source = ActiveRecordSectionHandler.Instance;
189 Initialize(Assembly.GetCallingAssembly(), source);
192 /// <summary>
193 /// Registers new assemblies in ActiveRecord
194 /// Usefull for dynamic assembly-adding after initialization
195 /// </summary>
196 /// <param name="assemblies"></param>
197 public static void RegisterAssemblies(params Assembly[] assemblies)
199 List<Type> types = new List<Type>();
201 foreach (Assembly assembly in assemblies)
203 CollectValidActiveRecordTypesFromAssembly(assembly, types, configSource);
206 RegisterTypes(types.ToArray());
209 /// <summary>
210 /// Registers new types in ActiveRecord
211 /// Usefull for dynamic type-adding after initialization
212 /// </summary>
213 /// <param name="types"></param>
214 public static void RegisterTypes(params Type[] types)
216 RegisterTypes(ActiveRecordBase.holder, configSource, types, false);
219 /// <summary>
220 /// Generates and executes the creation scripts for the database.
221 /// </summary>
222 public static void CreateSchema()
224 CheckInitialized();
226 foreach(Configuration config in ActiveRecordBase.holder.GetAllConfigurations())
228 SchemaExport export = CreateSchemaExport(config);
232 export.Create(false, true);
234 catch(Exception ex)
236 throw new ActiveRecordException("Could not create the schema", ex);
241 /// <summary>
242 /// Generates and executes the creation scripts for the database using
243 /// the specified baseClass to know which database it should create the schema for.
244 /// </summary>
245 public static void CreateSchema(Type baseClass)
247 CheckInitialized();
249 Configuration config = ActiveRecordBase.holder.GetConfiguration(baseClass);
251 SchemaExport export = CreateSchemaExport(config);
255 export.Create(false, true);
257 catch (Exception ex)
259 throw new ActiveRecordException("Could not create the schema", ex);
263 /// <summary>
264 /// Executes the specified script to create/drop/change the database schema
265 /// </summary>
266 public static void CreateSchemaFromFile(String scriptFileName)
268 CheckInitialized();
270 CreateSchemaFromFile(scriptFileName, ActiveRecordBase.holder.CreateSession(typeof(ActiveRecordBase)).Connection);
273 /// <summary>
274 /// Executes the specified script to create/drop/change the database schema
275 /// against the specified database connection
276 /// </summary>
277 public static void CreateSchemaFromFile(String scriptFileName, IDbConnection connection)
279 CheckInitialized();
281 if (connection == null)
283 throw new ArgumentNullException("connection");
286 String[] parts = ARSchemaCreator.OpenFileAndStripContents(scriptFileName);
287 ARSchemaCreator.ExecuteScriptParts(connection, parts);
290 /// <summary>
291 /// Generates and executes the Drop scripts for the database.
292 /// </summary>
293 public static void DropSchema()
295 CheckInitialized();
297 foreach(Configuration config in ActiveRecordBase.holder.GetAllConfigurations())
299 SchemaExport export = CreateSchemaExport(config);
303 export.Drop(false, true);
305 catch(Exception ex)
307 throw new ActiveRecordException("Could not drop the schema", ex);
312 /// <summary>
313 /// Generates and executes the Drop scripts for the database using
314 /// the specified baseClass to know which database it should create the scripts for.
315 /// </summary>
316 public static void DropSchema(Type baseClass)
318 CheckInitialized();
320 Configuration config = ActiveRecordBase.holder.GetConfiguration(baseClass);
322 SchemaExport export = CreateSchemaExport(config);
326 export.Drop(false, true);
328 catch (Exception ex)
330 throw new ActiveRecordException("Could not drop the schema", ex);
334 /// <summary>
335 /// Generates the drop scripts for the database saving them to the supplied file name.
336 /// </summary>
337 /// <remarks>
338 /// If ActiveRecord was configured to access more than one database, a file is going
339 /// to be generate for each, based on the path and the <c>fileName</c> specified.
340 /// </remarks>
341 public static void GenerateDropScripts(String fileName)
343 CheckInitialized();
345 bool isFirstExport = true;
346 int fileCount = 1;
348 foreach(Configuration config in ActiveRecordBase.holder.GetAllConfigurations())
350 SchemaExport export = CreateSchemaExport(config);
354 export.SetOutputFile(isFirstExport ? fileName : CreateAnotherFile(fileName, fileCount++));
355 export.Drop(false, false);
357 catch(Exception ex)
359 throw new ActiveRecordException("Could not drop the schema", ex);
362 isFirstExport = false;
366 /// <summary>
367 /// Generates the drop scripts for the database saving them to the supplied file name.
368 /// The baseType is used to identify which database should we act upon.
369 /// </summary>
370 public static void GenerateDropScripts(Type baseType, String fileName)
372 CheckInitialized();
374 Configuration config = ActiveRecordBase.holder.GetConfiguration(baseType);
376 SchemaExport export = CreateSchemaExport(config);
380 export.SetOutputFile(fileName);
381 export.Drop(false, false);
383 catch (Exception ex)
385 throw new ActiveRecordException("Could not generate drop schema scripts", ex);
389 /// <summary>
390 /// Generates the creation scripts for the database
391 /// </summary>
392 /// <remarks>
393 /// If ActiveRecord was configured to access more than one database, a file is going
394 /// to be generate for each, based on the path and the <c>fileName</c> specified.
395 /// </remarks>
396 public static void GenerateCreationScripts(String fileName)
398 CheckInitialized();
400 bool isFirstExport = true;
401 int fileCount = 1;
403 foreach(Configuration config in ActiveRecordBase.holder.GetAllConfigurations())
405 SchemaExport export = CreateSchemaExport(config);
409 export.SetOutputFile(isFirstExport ? fileName : CreateAnotherFile(fileName, fileCount++));
410 export.Create(false, false);
412 catch(Exception ex)
414 throw new ActiveRecordException("Could not create the schema", ex);
417 isFirstExport = false;
421 /// <summary>
422 /// Generates the creation scripts for the database
423 /// The baseType is used to identify which database should we act upon.
424 /// </summary>
425 public static void GenerateCreationScripts(Type baseType, String fileName)
427 CheckInitialized();
429 Configuration config = ActiveRecordBase.holder.GetConfiguration(baseType);
431 SchemaExport export = CreateSchemaExport(config);
435 export.SetOutputFile(fileName);
436 export.Create(false, false);
438 catch (Exception ex)
440 throw new ActiveRecordException("Could not create the schema scripts", ex);
444 /// <summary>
445 /// Intended to be used only by test cases
446 /// </summary>
447 public static void ResetInitializationFlag()
449 isInitialized = false;
452 private static ActiveRecordModelCollection BuildModels(ISessionFactoryHolder holder,
453 IConfigurationSource source,
454 IEnumerable<Type> types, bool ignoreProblematicTypes)
456 ActiveRecordModelBuilder builder = new ActiveRecordModelBuilder();
458 ActiveRecordModelCollection models = builder.Models;
460 foreach(Type type in types)
462 if (ShouldIgnoreType(type))
464 if (ignoreProblematicTypes)
466 continue;
468 else
470 throw new ActiveRecordException(
471 String.Format("Type `{0}` is registered already", type.FullName));
474 else if (IsConfiguredAsRootType(type))
476 if (TypeDefinesADatabaseBoundary(type))
478 SetUpConfiguration(source, type, holder);
479 continue;
481 else
483 throw new ActiveRecordException(
484 string.Format(
485 "Type `{0}` is not a valid root type. Make sure it is abstract and does not define a table itself.",
486 type.FullName));
489 else if (!IsActiveRecordType(type))
491 if (ignoreProblematicTypes)
493 continue;
495 else
497 throw new ActiveRecordException(
498 String.Format("Type `{0}` is not an ActiveRecord type. Use ActiveRecordAttributes to define one", type.FullName));
502 ActiveRecordModel model = builder.Create(type);
504 if (model == null)
506 throw new ActiveRecordException(
507 String.Format("ActiveRecordModel for `{0}` could not be created", type.FullName));
510 registeredTypes.Add(type, String.Empty);
512 if (ModelCreated != null)
514 ModelCreated(model, source);
518 return models;
521 private static bool IsConfiguredAsRootType(Type type)
523 return configSource.GetConfiguration(type) != null;
526 private static bool TypeDefinesADatabaseBoundary(Type type)
528 return (type.IsAbstract && !IsTypeHierarchyBase(type));
531 private static bool ShouldIgnoreType(Type type)
533 return (registeredTypes.ContainsKey(type) ||
534 type == typeof(ActiveRecordBase) ||
535 type == typeof(ActiveRecordValidationBase) ||
536 type == typeof(ActiveRecordHooksBase));
539 private static bool IsTypeHierarchyBase(ICustomAttributeProvider type)
541 if (type.IsDefined(typeof(JoinedBaseAttribute), false))
543 return true;
546 object[] attrs = type.GetCustomAttributes(typeof(ActiveRecordAttribute), false);
548 if (attrs != null && attrs.Length > 0)
550 ActiveRecordAttribute att = (ActiveRecordAttribute) attrs[0];
552 return att.DiscriminatorColumn != null;
555 return false;
558 private static void AddXmlToNHibernateCfg(ISessionFactoryHolder holder, ActiveRecordModelCollection models)
560 XmlGenerationVisitor xmlVisitor = new XmlGenerationVisitor();
561 AssemblyXmlGenerator assemblyXmlGenerator = new AssemblyXmlGenerator();
562 ISet assembliesGeneratedXmlFor = new HashedSet();
563 foreach(ActiveRecordModel model in models)
565 Configuration config = holder.GetConfiguration(holder.GetRootType(model.Type));
567 if (config==null)
569 throw new ActiveRecordException(
570 string.Format(
571 "Could not find configuration for {0} or its root type {1} this is usually an indication that the configuration has not been setup correctly.",
572 model.Type, holder.GetRootType(model.Type)));
575 if (!model.IsNestedType && !model.IsDiscriminatorSubClass && !model.IsJoinedSubClass)
577 xmlVisitor.Reset();
578 xmlVisitor.CreateXml(model);
580 String xml = xmlVisitor.Xml;
582 if (xml != String.Empty)
584 AddXmlString(config, xml, model);
588 if (!assembliesGeneratedXmlFor.Contains(model.Type.Assembly))
590 assembliesGeneratedXmlFor.Add(model.Type.Assembly);
592 string[] configurations = assemblyXmlGenerator.CreateXmlConfigurations(model.Type.Assembly);
594 foreach(string xml in configurations)
596 if (xml != string.Empty)
598 config.AddXmlString(xml);
605 private static void AddXmlString(Configuration config, string xml, ActiveRecordModel model)
609 config.AddXmlString(xml);
611 catch(Exception ex)
613 throw new ActiveRecordException("Error adding information from class " + model.Type.FullName + " to NHibernate. Check the inner exception for more information", ex);
617 private static Type[] GetExportedTypesFromAssembly(Assembly assembly)
621 return assembly.GetExportedTypes();
623 catch(Exception ex)
625 throw new ActiveRecordInitializationException(
626 "Error while loading the exported types from the assembly: " + assembly.FullName, ex);
630 private static SchemaExport CreateSchemaExport(Configuration cfg)
632 return new SchemaExport(cfg);
635 /// <summary>
636 /// Return true if the type has a [ActiveRecord] attribute
637 /// </summary>
638 private static bool IsActiveRecordType(ICustomAttributeProvider type)
640 return type.IsDefined(typeof(ActiveRecordAttribute), false);
643 private static void CheckInitialized()
645 if (ActiveRecordBase.holder == null)
647 throw new ActiveRecordException("Framework must be Initialized first.");
651 private static Configuration CreateConfiguration(IConfiguration config)
653 // hammett comments: I'm gonna test this off for a while
654 // ayende comments: removing this means that tests that using generic entities as base class will break
655 // NH do not support generic entities at the moemnt
656 Environment.UseReflectionOptimizer = false;
658 Configuration cfg = new Configuration();
660 foreach(IConfiguration childConfig in config.Children)
662 cfg.Properties.Add(childConfig.Name, childConfig.Value);
665 return cfg;
668 private static void SetUpConfiguration(IConfigurationSource source, Type type, ISessionFactoryHolder holder)
670 IConfiguration config = source.GetConfiguration(type);
672 if (config != null)
674 Configuration nconf = CreateConfiguration(config);
676 if (source.NamingStrategyImplementation != null)
678 Type namingStrategyType = source.NamingStrategyImplementation;
680 if (!typeof(INamingStrategy).IsAssignableFrom(namingStrategyType))
682 String message =
683 String.Format("The specified type {0} does " + "not implement the interface INamingStrategy",
684 namingStrategyType.FullName);
686 throw new ActiveRecordException(message);
689 nconf.SetNamingStrategy((INamingStrategy)Activator.CreateInstance(namingStrategyType));
692 holder.Register(type, nconf);
696 private static void RaiseSessionFactoryHolderCreated(ISessionFactoryHolder holder)
698 if (SessionFactoryHolderCreated != null)
700 SessionFactoryHolderCreated(holder);
704 private static ISessionFactoryHolder CreateSessionFactoryHolderImplementation(IConfigurationSource source)
706 if (source.SessionFactoryHolderImplementation != null)
708 Type sessionFactoryHolderType = source.SessionFactoryHolderImplementation;
710 if (!typeof(ISessionFactoryHolder).IsAssignableFrom(sessionFactoryHolderType))
712 String message =
713 String.Format("The specified type {0} does " + "not implement the interface ISessionFactoryHolder",
714 sessionFactoryHolderType.FullName);
716 throw new ActiveRecordException(message);
719 return (ISessionFactoryHolder) Activator.CreateInstance(sessionFactoryHolderType);
721 else
723 return new SessionFactoryHolder();
727 private static void RegisterTypes(ISessionFactoryHolder holder, IConfigurationSource source, IEnumerable<Type> types,
728 bool ignoreProblematicTypes)
730 lock(lockConfig)
732 ActiveRecordModelCollection models = BuildModels(holder, source, types, ignoreProblematicTypes);
734 GraphConnectorVisitor connectorVisitor = new GraphConnectorVisitor(models);
735 connectorVisitor.VisitNodes(models);
737 ModelsDelegate modelsCreatedHandler = ModelsCreated;
738 if (modelsCreatedHandler != null)
740 modelsCreatedHandler(models, source);
743 SemanticVerifierVisitor semanticVisitor = new SemanticVerifierVisitor(models);
744 semanticVisitor.VisitNodes(models);
746 ModelsDelegate modelsValidatedHandler = ModelsValidated;
747 if(modelsValidatedHandler != null)
749 modelsValidatedHandler(models, source);
752 AddXmlToNHibernateCfg(holder, models);
754 if (source.VerifyModelsAgainstDBSchema)
756 VerifySchema(models);
761 private static void VerifySchema(ActiveRecordModelCollection models)
763 foreach(ActiveRecordModel model in models)
765 if (!model.Type.IsAbstract)
769 ActiveRecordMediator.FindAll(model.Type, Expression.Sql("1=0"));
771 catch (Exception ex)
773 throw new ActiveRecordException("Error verifying the schema for model " + model.Type.Name, ex);
779 private static IThreadScopeInfo CreateThreadScopeInfoImplementation(IConfigurationSource source)
781 if (source.ThreadScopeInfoImplementation != null)
783 Type threadScopeType = source.ThreadScopeInfoImplementation;
785 if (!typeof(IThreadScopeInfo).IsAssignableFrom(threadScopeType))
787 String message =
788 String.Format("The specified type {0} does " + "not implement the interface IThreadScopeInfo",
789 threadScopeType.FullName);
791 throw new ActiveRecordInitializationException(message);
794 return (IThreadScopeInfo) Activator.CreateInstance(threadScopeType);
796 else
798 return new ThreadScopeInfo();
802 /// <summary>
803 /// Retrieve all classes decorated with ActiveRecordAttribute or that have been configured
804 /// as a AR base class.
805 /// </summary>
806 /// <param name="assembly">Assembly to retrieve types from</param>
807 /// <param name="list">Array to store retrieved types in</param>
808 /// <param name="source">IConfigurationSource to inspect AR base declarations from</param>
809 private static void CollectValidActiveRecordTypesFromAssembly(Assembly assembly, ICollection<Type> list,
810 IConfigurationSource source)
812 Type[] types = GetExportedTypesFromAssembly(assembly);
814 foreach(Type type in types)
816 if (IsActiveRecordType(type) || source.GetConfiguration(type) != null)
818 list.Add(type);
823 /// <summary>
824 /// Generate a file name based on the original file name specified, using the
825 /// count to give it some order.
826 /// </summary>
827 /// <param name="originalFileName"></param>
828 /// <param name="fileCount"></param>
829 /// <returns></returns>
830 private static string CreateAnotherFile(string originalFileName, int fileCount)
832 string path = Path.GetDirectoryName(originalFileName);
833 string fileName = Path.GetFileNameWithoutExtension(originalFileName);
834 string extension = Path.GetExtension(originalFileName);
836 return Path.Combine(path, string.Format("{0}_{1}{2}", fileName, fileCount, extension));