- Fixed MR-84
[castle.git] / ActiveRecord / Castle.ActiveRecord / Framework / Internal / ActiveRecordModelBuilder.cs
blob0a1e49ac72cb40332299953987a201ee8e3a4f08
1 // Copyright 2004-2007 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.Framework.Internal
17 using System;
18 using System.Collections;
19 using System.Reflection;
20 using Castle.Components.Validator;
22 /// <summary>
23 /// Bulids an <see cref="ActiveRecordModel"/> from a type and does some inital validation.
24 /// </summary>
25 public class ActiveRecordModelBuilder
27 private static readonly BindingFlags DefaultBindingFlags = BindingFlags.DeclaredOnly | BindingFlags.Public |
28 BindingFlags.Instance | BindingFlags.NonPublic;
30 private static readonly BindingFlags FieldDefaultBindingFlags = BindingFlags.DeclaredOnly | BindingFlags.Public |
31 BindingFlags.NonPublic | BindingFlags.Instance;
33 private readonly ActiveRecordModelCollection coll = new ActiveRecordModelCollection();
35 private static readonly IValidatorRegistry validatorRegistry = new CachedValidationRegistry();
37 /// <summary>
38 /// Creates a <see cref="ActiveRecordModel"/> from the specified type.
39 /// </summary>
40 /// <param name="type">The type.</param>
41 /// <returns></returns>
42 public ActiveRecordModel Create(Type type)
44 if (type == null) throw new ArgumentNullException("type");
46 if (type.IsDefined(typeof(ActiveRecordSkipAttribute), false)) return null;
48 ActiveRecordModel model = new ActiveRecordModel(type);
50 coll.Add(model);
52 PopulateModel(model, type);
54 ActiveRecordBase.Register(type, model);
56 return model;
59 /// <summary>
60 /// Gets the models.
61 /// </summary>
62 /// <value>The models.</value>
63 public ActiveRecordModelCollection Models
65 get { return coll; }
68 /// <summary>
69 /// Gets the validator registry used to create the validators
70 /// </summary>
71 public static IValidatorRegistry ValidatorRegistry
73 get { return validatorRegistry; }
76 /// <summary>
77 /// Populates the model from tye type
78 /// </summary>
79 /// <param name="model">The model.</param>
80 /// <param name="type">The type.</param>
81 private static void PopulateModel(ActiveRecordModel model, Type type)
83 ProcessActiveRecordAttribute(type, model);
85 ProcessImports(type, model);
87 ProcessJoinedBaseAttribute(type, model);
89 ProcessProperties(type, model);
91 ProcessFields(type, model);
94 private static void ProcessImports(Type type, ActiveRecordModel model)
96 object[] attrs = type.GetCustomAttributes(typeof(ImportAttribute), false);
98 foreach (ImportAttribute att in attrs)
100 ImportModel im = new ImportModel(att);
101 model.Imports.Add(im);
105 private static void ProcessJoinedBaseAttribute(Type type, ActiveRecordModel model)
107 model.IsJoinedSubClassBase = type.IsDefined(typeof(JoinedBaseAttribute), false);
110 private static void PopulateActiveRecordAttribute(ActiveRecordAttribute attribute, ActiveRecordModel model)
112 model.ActiveRecordAtt = attribute;
114 if (attribute.DiscriminatorColumn != null)
116 model.IsDiscriminatorBase = true;
118 if (attribute.DiscriminatorValue == null)
120 throw new ActiveRecordException(
121 String.Format("You must specify a discriminator value for the type {0}", model.Type.FullName));
124 else if (attribute.DiscriminatorType != null)
126 throw new ActiveRecordException(
127 String.Format("The usage of DiscriminatorType for {0} is meaningless", model.Type.FullName));
130 if (model.ActiveRecordAtt.Table == null)
132 string safename = GetSafeName(model.Type.Name);
133 model.ActiveRecordAtt.Table = ActiveRecordModel.pluralizeTableNames
134 ? Inflector.Pluralize(safename)
135 : safename;
139 /// <summary>
140 /// Remove the generic part from the type name.
141 /// </summary>
142 /// <param name="name"></param>
143 /// <returns></returns>
144 private static string GetSafeName(string name)
146 int index = name.IndexOf("`");
148 if (index == -1) return name;
150 return name.Substring(0, index);
153 private static void ProcessFields(Type type, ActiveRecordModel model)
155 //Check persistent fields of the base class as well
156 if (ShouldCheckBase(type))
158 ProcessFields(type.BaseType, model);
161 FieldInfo[] fields = type.GetFields(FieldDefaultBindingFlags);
163 foreach (FieldInfo field in fields)
165 if (field.IsDefined(typeof(FieldAttribute), false))
167 FieldAttribute fieldAtt = field.GetCustomAttributes(typeof(FieldAttribute), false)[0] as FieldAttribute;
169 model.Fields.Add(new FieldModel(field, fieldAtt));
174 private static void ProcessProperties(Type type, ActiveRecordModel model)
176 // Check persistent properties of the base class as well
177 if (ShouldCheckBase(type))
179 ProcessProperties(type.BaseType, model);
182 PropertyInfo[] props = type.GetProperties(DefaultBindingFlags);
184 foreach (PropertyInfo prop in props)
186 bool isArProperty = false;
187 AnyModel anyModel;
188 HasManyToAnyModel hasManyToAnyModel;
190 object[] valAtts = prop.GetCustomAttributes(typeof(AbstractValidationAttribute), true);
192 foreach (AbstractValidationAttribute valAtt in valAtts)
194 IValidator validator = valAtt.Build();
195 validator.Initialize(validatorRegistry, prop);
197 model.Validators.Add(validator);
200 foreach (object attribute in prop.GetCustomAttributes(false))
202 if (attribute is PrimaryKeyAttribute)
204 PrimaryKeyAttribute propAtt = attribute as PrimaryKeyAttribute;
205 isArProperty = true;
207 // Joined Subclasses must not have PrimaryKey
208 if (type.IsDefined(typeof(JoinedBaseAttribute), true) && // JoinedBase in a superclass
209 !type.IsDefined(typeof(JoinedBaseAttribute), false)) // but not here
211 throw new ActiveRecordException("You can't specify a PrimaryKeyAttribute in a joined subclass. " +
212 "Check type " + model.Type.FullName);
215 if (prop.PropertyType.IsDefined(typeof(CompositeKeyAttribute), true))
217 object[] att = prop.PropertyType.GetCustomAttributes(typeof(CompositeKeyAttribute), true);
219 CompositeKeyAttribute cAtt = att[0] as CompositeKeyAttribute;
221 model.CompositeKey = new CompositeKeyModel(prop, cAtt);
223 else
225 model.PrimaryKey = new PrimaryKeyModel(prop, propAtt);
228 else if (attribute is CompositeKeyAttribute)
230 CompositeKeyAttribute propAtt = attribute as CompositeKeyAttribute;
231 isArProperty = true;
233 model.CompositeKey = new CompositeKeyModel(prop, propAtt);
235 else if (attribute is AnyAttribute)
237 AnyAttribute anyAtt = attribute as AnyAttribute;
238 isArProperty = true;
239 anyModel = new AnyModel(prop, anyAtt);
240 model.Anys.Add(anyModel);
242 CollectMetaValues(anyModel.MetaValues, prop);
244 else if (attribute is PropertyAttribute)
246 PropertyAttribute propAtt = attribute as PropertyAttribute;
247 isArProperty = true;
249 model.Properties.Add(new PropertyModel(prop, propAtt));
251 else if (attribute is NestedAttribute)
253 NestedAttribute propAtt = attribute as NestedAttribute;
254 isArProperty = true;
256 ActiveRecordModel nestedModel = new ActiveRecordModel(prop.PropertyType);
258 nestedModel.IsNestedType = true;
260 Type nestedType = propAtt.MapType ?? prop.PropertyType;
261 nestedModel.IsNestedCompositeType = model.IsNestedCompositeType;
262 ProcessProperties(nestedType, nestedModel);
263 ProcessFields(nestedType, nestedModel);
265 NestedModel nested = new NestedModel(prop, propAtt, nestedModel);
266 nestedModel.ParentNested = nested;
268 model.Components.Add(nested);
270 else if (attribute is NestedParentReferenceAttribute)
272 NestedParentReferenceAttribute nestedParentAtt = attribute as NestedParentReferenceAttribute;
273 isArProperty = true;
275 model.ComponentParent.Add(new NestedParentReferenceModel(prop, nestedParentAtt));
277 else if (attribute is JoinedKeyAttribute)
279 JoinedKeyAttribute propAtt = attribute as JoinedKeyAttribute;
280 isArProperty = true;
282 if (model.Key != null)
284 throw new ActiveRecordException("You can't specify more than one JoinedKeyAttribute. " +
285 "Check type " + model.Type.FullName);
288 model.Key = new KeyModel(prop, propAtt);
290 else if (attribute is VersionAttribute)
292 VersionAttribute propAtt = attribute as VersionAttribute;
293 isArProperty = true;
295 if (model.Version != null)
297 throw new ActiveRecordException("You can't specify more than one VersionAttribute. " +
298 "Check type " + model.Type.FullName);
301 model.Version = new VersionModel(prop, propAtt);
303 else if (attribute is TimestampAttribute)
305 TimestampAttribute propAtt = attribute as TimestampAttribute;
306 isArProperty = true;
308 if (model.Timestamp != null)
310 throw new ActiveRecordException("You can't specify more than one TimestampAttribute. " +
311 "Check type " + model.Type.FullName);
314 model.Timestamp = new TimestampModel(prop, propAtt);
316 // Relations
317 else if (attribute is OneToOneAttribute)
319 OneToOneAttribute propAtt = attribute as OneToOneAttribute;
320 isArProperty = true;
322 model.OneToOnes.Add(new OneToOneModel(prop, propAtt));
324 else if (attribute is BelongsToAttribute)
326 BelongsToAttribute propAtt = attribute as BelongsToAttribute;
327 isArProperty = true;
329 model.BelongsTo.Add(new BelongsToModel(prop, propAtt));
331 // The ordering is important here, HasManyToAny must comes before HasMany!
332 else if (attribute is HasManyToAnyAttribute)
334 HasManyToAnyAttribute propAtt = attribute as HasManyToAnyAttribute;
335 isArProperty = true;
337 hasManyToAnyModel = new HasManyToAnyModel(prop, propAtt);
338 model.HasManyToAny.Add(hasManyToAnyModel);
340 CollectMetaValues(hasManyToAnyModel.MetaValues, prop);
342 else if (attribute is HasManyAttribute)
344 HasManyAttribute propAtt = attribute as HasManyAttribute;
345 isArProperty = true;
347 HasManyModel hasManyModel = new HasManyModel(prop, propAtt);
348 if (propAtt.DependentObjects)
350 ActiveRecordModel dependentObjectModel = new ActiveRecordModel(propAtt.MapType);
351 dependentObjectModel.IsNestedType = true;
352 dependentObjectModel.IsNestedCompositeType = true;
353 ProcessProperties(propAtt.MapType, dependentObjectModel);
355 hasManyModel.DependentObjectModel = new DependentObjectModel(prop, propAtt, dependentObjectModel);
357 model.HasMany.Add(hasManyModel);
359 else if (attribute is HasAndBelongsToManyAttribute)
361 HasAndBelongsToManyAttribute propAtt = attribute as HasAndBelongsToManyAttribute;
362 isArProperty = true;
364 model.HasAndBelongsToMany.Add(new HasAndBelongsToManyModel(prop, propAtt));
366 else if (attribute is Any.MetaValueAttribute)
368 if (prop.GetCustomAttributes(typeof(HasManyToAnyAttribute), false).Length == 0 &&
369 prop.GetCustomAttributes(typeof(AnyAttribute), false).Length == 0
371 throw new ActiveRecordException(
372 "You can't specify an Any.MetaValue without specifying the Any or HasManyToAny attribute. " +
373 "Check type " + prop.DeclaringType.FullName);
375 else if (attribute is CompositeUserTypeAttribute)
377 CompositeUserTypeAttribute propAtt = attribute as CompositeUserTypeAttribute;
378 isArProperty = true;
380 model.CompositeUserType.Add(new CompositeUserTypeModel(prop, propAtt));
383 if (attribute is CollectionIDAttribute)
385 CollectionIDAttribute propAtt = attribute as CollectionIDAttribute;
387 model.CollectionIDs.Add(new CollectionIDModel(prop, propAtt));
389 if (attribute is HiloAttribute)
391 HiloAttribute propAtt = attribute as HiloAttribute;
393 model.Hilos.Add(new HiloModel(prop, propAtt));
397 if (!isArProperty)
399 model.NotMappedProperties.Add(prop);
404 private static void CollectMetaValues(IList metaStore, PropertyInfo prop)
406 if (metaStore == null)
407 throw new ArgumentNullException("metaStore");
409 Any.MetaValueAttribute[] metaValues =
410 prop.GetCustomAttributes(typeof(Any.MetaValueAttribute), false) as Any.MetaValueAttribute[];
412 if (metaValues == null || metaValues.Length == 0)
413 return;
415 foreach (Any.MetaValueAttribute attribute in metaValues)
417 metaStore.Add(attribute);
421 private static bool ShouldCheckBase(Type type)
423 // Changed as suggested http://support.castleproject.org/jira/browse/AR-40
424 bool shouldCheck = IsRootType(type);
426 if (shouldCheck) // Perform more checks
428 Type basetype = type.BaseType;
430 while (basetype != typeof(object))
432 if (basetype.IsDefined(typeof(JoinedBaseAttribute), false)) return false;
434 object[] attrs = basetype.GetCustomAttributes(typeof(ActiveRecordAttribute), false);
436 if (attrs.Length != 0)
438 ActiveRecordAttribute arAttribute = attrs[0] as ActiveRecordAttribute;
439 if (arAttribute.DiscriminatorColumn != null)
440 return false;
442 else
444 return true;
447 basetype = basetype.BaseType;
451 return shouldCheck;
454 private static bool IsRootType(Type type)
456 bool isRootType = type.BaseType != typeof(object) &&
457 type.BaseType != typeof(ActiveRecordBase) &&
458 type.BaseType != typeof(ActiveRecordValidationBase);
459 // && !type.BaseType.IsDefined(typeof(ActiveRecordAttribute), false);
461 // generic check
462 if (type.BaseType.IsGenericType)
464 isRootType = type.BaseType.GetGenericTypeDefinition() != typeof(ActiveRecordBase<>) &&
465 type.BaseType.GetGenericTypeDefinition() != typeof(ActiveRecordValidationBase<>);
468 return isRootType;
471 private static void ProcessActiveRecordAttribute(Type type, ActiveRecordModel model)
473 object[] attrs = type.GetCustomAttributes(typeof(ActiveRecordAttribute), false);
475 if (attrs.Length == 0)
477 throw new ActiveRecordException(
478 String.Format("Type {0} is not using the ActiveRecordAttribute, which is obligatory.", type.FullName));
481 ActiveRecordAttribute arAttribute = attrs[0] as ActiveRecordAttribute;
483 PopulateActiveRecordAttribute(arAttribute, model);