1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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
18 using System
.Collections
.Generic
;
19 using System
.Reflection
;
20 using Castle
.Components
.Validator
;
23 /// Bulids an <see cref="ActiveRecordModel"/> from a type and does some inital validation.
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 static readonly IValidatorRegistry validatorRegistry
= new CachedValidationRegistry();
35 private readonly ActiveRecordModelCollection coll
= new ActiveRecordModelCollection();
37 private IModelBuilderExtension extension
;
40 /// Creates a <see cref="ActiveRecordModel"/> from the specified type.
42 /// <param name="type">The type.</param>
43 /// <returns></returns>
44 public ActiveRecordModel
Create(Type type
)
46 if (type
== null) throw new ArgumentNullException("type");
48 if (type
.IsDefined(typeof(ActiveRecordSkipAttribute
), false)) return null;
50 ActiveRecordModel model
= new ActiveRecordModel(type
);
54 PopulateModel(model
, type
);
56 ActiveRecordBase
.Register(type
, model
);
62 /// Sets the extension.
64 /// <param name="extension">The extension.</param>
65 public void SetExtension(IModelBuilderExtension extension
)
67 this.extension
= extension
;
73 /// <value>The models.</value>
74 public ActiveRecordModelCollection Models
80 /// Gets the validator registry used to create the validators
82 public static IValidatorRegistry ValidatorRegistry
84 get { return validatorRegistry; }
88 /// Populates the model from tye type
90 /// <param name="model">The model.</param>
91 /// <param name="type">The type.</param>
92 private void PopulateModel(ActiveRecordModel model
, Type type
)
94 if (extension
!= null)
96 extension
.ProcessClass(type
, model
);
99 ProcessActiveRecordAttribute(type
, model
);
101 ProcessImports(type
, model
);
103 ProcessJoinedBaseAttribute(type
, model
);
105 ProcessProperties(type
, model
);
107 ProcessFields(type
, model
);
110 private static void ProcessImports(Type type
, ActiveRecordModel model
)
112 object[] attrs
= type
.GetCustomAttributes(typeof(ImportAttribute
), false);
114 foreach (ImportAttribute att
in attrs
)
116 ImportModel im
= new ImportModel(att
);
117 model
.Imports
.Add(im
);
121 private static void ProcessJoinedBaseAttribute(Type type
, ActiveRecordModel model
)
123 model
.IsJoinedSubClassBase
= type
.IsDefined(typeof(JoinedBaseAttribute
), false);
126 private static void PopulateActiveRecordAttribute(ActiveRecordAttribute attribute
, ActiveRecordModel model
)
128 model
.ActiveRecordAtt
= attribute
;
130 if (attribute
.DiscriminatorColumn
!= null)
132 model
.IsDiscriminatorBase
= true;
134 if (attribute
.DiscriminatorValue
== null)
136 throw new ActiveRecordException(
137 String
.Format("You must specify a discriminator value for the type {0}", model
.Type
.FullName
));
140 else if (attribute
.DiscriminatorType
!= null)
142 throw new ActiveRecordException(
143 String
.Format("The usage of DiscriminatorType for {0} is meaningless", model
.Type
.FullName
));
146 if (model
.ActiveRecordAtt
.Table
== null)
148 string safename
= GetSafeName(model
.Type
.Name
);
149 model
.ActiveRecordAtt
.Table
= ActiveRecordModel
.pluralizeTableNames
150 ? Inflector
.Pluralize(safename
)
156 /// Remove the generic part from the type name.
158 /// <param name="name"></param>
159 /// <returns></returns>
160 private static string GetSafeName(string name
)
162 int index
= name
.IndexOf("`");
164 if (index
== -1) return name
;
166 return name
.Substring(0, index
);
169 private void ProcessFields(Type type
, ActiveRecordModel model
)
171 //Check persistent fields of the base class as well
172 if (ShouldCheckBase(type
))
174 ProcessFields(type
.BaseType
, model
);
177 FieldInfo
[] fields
= type
.GetFields(FieldDefaultBindingFlags
);
179 foreach (FieldInfo field
in fields
)
181 if (field
.IsDefined(typeof(FieldAttribute
), false))
183 FieldAttribute fieldAtt
= field
.GetCustomAttributes(typeof(FieldAttribute
), false)[0] as FieldAttribute
;
185 model
.Fields
.Add(new FieldModel(field
, fieldAtt
));
188 if (extension
!= null)
190 extension
.ProcessField(field
, model
);
195 private void ProcessProperties(Type type
, ActiveRecordModel model
)
197 // Check persistent properties of the base class as well
198 if (ShouldCheckBase(type
))
200 ProcessProperties(type
.BaseType
, model
);
203 PropertyInfo
[] props
= type
.GetProperties(DefaultBindingFlags
);
205 foreach(PropertyInfo prop
in props
)
207 bool isArProperty
= false;
209 HasManyToAnyModel hasManyToAnyModel
;
211 if (extension
!= null)
213 extension
.ProcessProperty(prop
, model
);
216 object[] valAtts
= prop
.GetCustomAttributes(typeof(AbstractValidationAttribute
), true);
218 foreach(AbstractValidationAttribute valAtt
in valAtts
)
220 IValidator validator
= valAtt
.Build();
221 validator
.Initialize(validatorRegistry
, prop
);
223 model
.Validators
.Add(validator
);
226 foreach(object attribute
in prop
.GetCustomAttributes(false))
228 if (attribute
is PrimaryKeyAttribute
)
230 PrimaryKeyAttribute propAtt
= attribute
as PrimaryKeyAttribute
;
233 // Joined Subclasses must not have PrimaryKey
234 if (type
.IsDefined(typeof(JoinedBaseAttribute
), true) && // JoinedBase in a superclass
235 !type
.IsDefined(typeof(JoinedBaseAttribute
), false)) // but not here
237 throw new ActiveRecordException("You can't specify a PrimaryKeyAttribute in a joined subclass. " +
238 "Check type " + model
.Type
.FullName
);
241 if (prop
.PropertyType
.IsDefined(typeof(CompositeKeyAttribute
), true))
243 object[] att
= prop
.PropertyType
.GetCustomAttributes(typeof(CompositeKeyAttribute
), true);
245 CompositeKeyAttribute cAtt
= att
[0] as CompositeKeyAttribute
;
247 model
.CompositeKey
= new CompositeKeyModel(prop
, cAtt
);
251 if (!propAtt
.IsOverride
&& model
.PrimaryKey
!= null)
253 throw new ActiveRecordException("You can't specify more than one PrimaryKeyAttribute in a " +
254 "class. Check type " + model
.Type
.FullName
);
257 model
.PrimaryKey
= new PrimaryKeyModel(prop
, propAtt
);
260 else if (attribute
is CompositeKeyAttribute
)
262 CompositeKeyAttribute propAtt
= attribute
as CompositeKeyAttribute
;
265 model
.CompositeKey
= new CompositeKeyModel(prop
, propAtt
);
267 else if (attribute
is AnyAttribute
)
269 AnyAttribute anyAtt
= attribute
as AnyAttribute
;
271 anyModel
= new AnyModel(prop
, anyAtt
);
272 model
.Anys
.Add(anyModel
);
274 CollectMetaValues(anyModel
.MetaValues
, prop
);
276 else if (attribute
is PropertyAttribute
)
278 PropertyAttribute propAtt
= attribute
as PropertyAttribute
;
281 // If this property overrides a base class property remove the old one
282 if (propAtt
.IsOverride
)
284 for (int index
= 0; index
< model
.Properties
.Count
; ++index
)
286 PropertyModel oldModel
= (PropertyModel
)model
.Properties
[index
];
288 if (oldModel
.Property
.Name
== prop
.Name
)
290 model
.Properties
.RemoveAt(index
);
296 PropertyModel propModel
= new PropertyModel(prop
, propAtt
);
297 model
.Properties
.Add(propModel
);
298 model
.PropertyDictionary
[prop
.Name
] = propModel
;
300 else if (attribute
is NestedAttribute
)
302 NestedAttribute propAtt
= attribute
as NestedAttribute
;
305 ActiveRecordModel nestedModel
= new ActiveRecordModel(prop
.PropertyType
);
307 nestedModel
.IsNestedType
= true;
309 Type nestedType
= propAtt
.MapType
?? prop
.PropertyType
;
310 nestedModel
.IsNestedCompositeType
= model
.IsNestedCompositeType
;
311 ProcessProperties(nestedType
, nestedModel
);
312 ProcessFields(nestedType
, nestedModel
);
314 NestedModel nested
= new NestedModel(prop
, propAtt
, nestedModel
);
315 nestedModel
.ParentNested
= nested
;
317 model
.Components
.Add(nested
);
319 else if (attribute
is NestedParentReferenceAttribute
)
321 NestedParentReferenceAttribute nestedParentAtt
= attribute
as NestedParentReferenceAttribute
;
324 model
.ComponentParent
.Add(new NestedParentReferenceModel(prop
, nestedParentAtt
));
326 else if (attribute
is JoinedKeyAttribute
)
328 JoinedKeyAttribute propAtt
= attribute
as JoinedKeyAttribute
;
331 if (model
.Key
!= null)
333 throw new ActiveRecordException("You can't specify more than one JoinedKeyAttribute. " +
334 "Check type " + model
.Type
.FullName
);
337 model
.Key
= new KeyModel(prop
, propAtt
);
339 else if (attribute
is VersionAttribute
)
341 VersionAttribute propAtt
= attribute
as VersionAttribute
;
344 if (model
.Version
!= null)
346 throw new ActiveRecordException("You can't specify more than one VersionAttribute. " +
347 "Check type " + model
.Type
.FullName
);
350 model
.Version
= new VersionModel(prop
, propAtt
);
352 else if (attribute
is TimestampAttribute
)
354 TimestampAttribute propAtt
= attribute
as TimestampAttribute
;
357 if (model
.Timestamp
!= null)
359 throw new ActiveRecordException("You can't specify more than one TimestampAttribute. " +
360 "Check type " + model
.Type
.FullName
);
363 model
.Timestamp
= new TimestampModel(prop
, propAtt
);
366 else if (attribute
is OneToOneAttribute
)
368 OneToOneAttribute propAtt
= attribute
as OneToOneAttribute
;
371 model
.OneToOnes
.Add(new OneToOneModel(prop
, propAtt
));
373 else if (attribute
is BelongsToAttribute
)
375 BelongsToAttribute propAtt
= attribute
as BelongsToAttribute
;
378 BelongsToModel btModel
= new BelongsToModel(prop
, propAtt
);
379 model
.BelongsTo
.Add(btModel
);
380 model
.BelongsToDictionary
[prop
.Name
] = btModel
;
382 if (extension
!= null)
384 extension
.ProcessBelongsTo(prop
, btModel
, model
);
387 // The ordering is important here, HasManyToAny must comes before HasMany!
388 else if (attribute
is HasManyToAnyAttribute
)
390 HasManyToAnyAttribute propAtt
= attribute
as HasManyToAnyAttribute
;
393 hasManyToAnyModel
= new HasManyToAnyModel(prop
, propAtt
);
394 model
.HasManyToAny
.Add(hasManyToAnyModel
);
395 model
.HasManyToAnyDictionary
[prop
.Name
] = hasManyToAnyModel
;
397 CollectMetaValues(hasManyToAnyModel
.MetaValues
, prop
);
399 if (extension
!= null)
401 extension
.ProcessHasManyToAny(prop
, hasManyToAnyModel
, model
);
404 else if (attribute
is HasManyAttribute
)
406 HasManyAttribute propAtt
= attribute
as HasManyAttribute
;
409 HasManyModel hasManyModel
= new HasManyModel(prop
, propAtt
);
410 if (propAtt
.DependentObjects
)
412 ActiveRecordModel dependentObjectModel
= new ActiveRecordModel(propAtt
.MapType
);
413 dependentObjectModel
.IsNestedType
= true;
414 dependentObjectModel
.IsNestedCompositeType
= true;
415 ProcessProperties(propAtt
.MapType
, dependentObjectModel
);
417 hasManyModel
.DependentObjectModel
= new DependentObjectModel(prop
, propAtt
, dependentObjectModel
);
419 model
.HasMany
.Add(hasManyModel
);
420 model
.HasManyDictionary
[prop
.Name
] = hasManyModel
;
422 if (extension
!= null)
424 extension
.ProcessHasMany(prop
, hasManyModel
, model
);
427 else if (attribute
is HasAndBelongsToManyAttribute
)
429 HasAndBelongsToManyAttribute propAtt
= attribute
as HasAndBelongsToManyAttribute
;
432 HasAndBelongsToManyModel habtManyModel
= new HasAndBelongsToManyModel(prop
, propAtt
);
433 model
.HasAndBelongsToMany
.Add(habtManyModel
);
434 model
.HasAndBelongsToManyDictionary
[prop
.Name
] = habtManyModel
;
436 if (extension
!= null)
438 extension
.ProcessHasAndBelongsToMany(prop
, habtManyModel
, model
);
441 else if (attribute
is Any
.MetaValueAttribute
)
443 if (prop
.GetCustomAttributes(typeof(HasManyToAnyAttribute
), false).Length
== 0 &&
444 prop
.GetCustomAttributes(typeof(AnyAttribute
), false).Length
== 0
446 throw new ActiveRecordException(
447 "You can't specify an Any.MetaValue without specifying the Any or HasManyToAny attribute. " +
448 "Check type " + prop
.DeclaringType
.FullName
);
450 else if (attribute
is CompositeUserTypeAttribute
)
452 CompositeUserTypeAttribute propAtt
= attribute
as CompositeUserTypeAttribute
;
455 model
.CompositeUserType
.Add(new CompositeUserTypeModel(prop
, propAtt
));
458 if (attribute
is CollectionIDAttribute
)
460 CollectionIDAttribute propAtt
= attribute
as CollectionIDAttribute
;
462 model
.CollectionIDs
.Add(new CollectionIDModel(prop
, propAtt
));
464 if (attribute
is HiloAttribute
)
466 HiloAttribute propAtt
= attribute
as HiloAttribute
;
468 model
.Hilos
.Add(new HiloModel(prop
, propAtt
));
474 model
.NotMappedProperties
.Add(prop
);
479 private static void CollectMetaValues(IList
<Any
.MetaValueAttribute
> metaStore
, PropertyInfo prop
)
481 if (metaStore
== null)
482 throw new ArgumentNullException("metaStore");
484 Any
.MetaValueAttribute
[] metaValues
=
485 prop
.GetCustomAttributes(typeof(Any
.MetaValueAttribute
), false) as Any
.MetaValueAttribute
[];
487 if (metaValues
== null || metaValues
.Length
== 0)
490 foreach (Any
.MetaValueAttribute attribute
in metaValues
)
492 metaStore
.Add(attribute
);
496 private static bool ShouldCheckBase(Type type
)
498 // Changed as suggested http://support.castleproject.org/jira/browse/AR-40
499 bool shouldCheck
= IsRootType(type
);
501 if (shouldCheck
) // Perform more checks
503 Type basetype
= type
.BaseType
;
505 while (basetype
!= typeof(object))
507 if (basetype
.IsDefined(typeof(JoinedBaseAttribute
), false)) return false;
509 object[] attrs
= basetype
.GetCustomAttributes(typeof(ActiveRecordAttribute
), false);
511 if (attrs
.Length
!= 0)
513 ActiveRecordAttribute arAttribute
= attrs
[0] as ActiveRecordAttribute
;
514 if (arAttribute
.DiscriminatorColumn
!= null)
522 basetype
= basetype
.BaseType
;
529 private static bool IsRootType(Type type
)
531 bool isRootType
= type
.BaseType
!= typeof(object) &&
532 type
.BaseType
!= typeof(ActiveRecordBase
) &&
533 type
.BaseType
!= typeof(ActiveRecordValidationBase
);
534 // && !type.BaseType.IsDefined(typeof(ActiveRecordAttribute), false);
537 if (type
.BaseType
.IsGenericType
)
539 isRootType
= type
.BaseType
.GetGenericTypeDefinition() != typeof(ActiveRecordBase
<>) &&
540 type
.BaseType
.GetGenericTypeDefinition() != typeof(ActiveRecordValidationBase
<>);
546 private static void ProcessActiveRecordAttribute(Type type
, ActiveRecordModel model
)
548 object[] attrs
= type
.GetCustomAttributes(typeof(ActiveRecordAttribute
), false);
550 if (attrs
.Length
== 0)
552 throw new ActiveRecordException(
553 String
.Format("Type {0} is not using the ActiveRecordAttribute, which is obligatory.", type
.FullName
));
556 ActiveRecordAttribute arAttribute
= attrs
[0] as ActiveRecordAttribute
;
558 PopulateActiveRecordAttribute(arAttribute
, model
);