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 readonly ActiveRecordModelCollection coll
= new ActiveRecordModelCollection();
35 private static readonly IValidatorRegistry validatorRegistry
= new CachedValidationRegistry();
38 /// Creates a <see cref="ActiveRecordModel"/> from the specified type.
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
);
52 PopulateModel(model
, type
);
54 ActiveRecordBase
.Register(type
, model
);
62 /// <value>The models.</value>
63 public ActiveRecordModelCollection Models
69 /// Gets the validator registry used to create the validators
71 public static IValidatorRegistry ValidatorRegistry
73 get { return validatorRegistry; }
77 /// Populates the model from tye type
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
)
140 /// Remove the generic part from the type name.
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;
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
;
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
);
225 if (!propAtt
.IsOverride
&& model
.PrimaryKey
!= null)
227 throw new ActiveRecordException("You can't specify more than one PrimaryKeyAttribute in a " +
228 "class. Check type " + model
.Type
.FullName
);
231 model
.PrimaryKey
= new PrimaryKeyModel(prop
, propAtt
);
234 else if (attribute
is CompositeKeyAttribute
)
236 CompositeKeyAttribute propAtt
= attribute
as CompositeKeyAttribute
;
239 model
.CompositeKey
= new CompositeKeyModel(prop
, propAtt
);
241 else if (attribute
is AnyAttribute
)
243 AnyAttribute anyAtt
= attribute
as AnyAttribute
;
245 anyModel
= new AnyModel(prop
, anyAtt
);
246 model
.Anys
.Add(anyModel
);
248 CollectMetaValues(anyModel
.MetaValues
, prop
);
250 else if (attribute
is PropertyAttribute
)
252 PropertyAttribute propAtt
= attribute
as PropertyAttribute
;
255 // If this property overrides a base class property remove the old one
256 if (propAtt
.IsOverride
)
258 for (int index
= 0; index
< model
.Properties
.Count
; ++index
)
260 PropertyModel oldModel
= (PropertyModel
)model
.Properties
[index
];
262 if (oldModel
.Property
.Name
== prop
.Name
)
264 model
.Properties
.RemoveAt(index
);
270 model
.Properties
.Add(new PropertyModel(prop
, propAtt
));
272 else if (attribute
is NestedAttribute
)
274 NestedAttribute propAtt
= attribute
as NestedAttribute
;
277 ActiveRecordModel nestedModel
= new ActiveRecordModel(prop
.PropertyType
);
279 nestedModel
.IsNestedType
= true;
281 Type nestedType
= propAtt
.MapType
?? prop
.PropertyType
;
282 nestedModel
.IsNestedCompositeType
= model
.IsNestedCompositeType
;
283 ProcessProperties(nestedType
, nestedModel
);
284 ProcessFields(nestedType
, nestedModel
);
286 NestedModel nested
= new NestedModel(prop
, propAtt
, nestedModel
);
287 nestedModel
.ParentNested
= nested
;
289 model
.Components
.Add(nested
);
291 else if (attribute
is NestedParentReferenceAttribute
)
293 NestedParentReferenceAttribute nestedParentAtt
= attribute
as NestedParentReferenceAttribute
;
296 model
.ComponentParent
.Add(new NestedParentReferenceModel(prop
, nestedParentAtt
));
298 else if (attribute
is JoinedKeyAttribute
)
300 JoinedKeyAttribute propAtt
= attribute
as JoinedKeyAttribute
;
303 if (model
.Key
!= null)
305 throw new ActiveRecordException("You can't specify more than one JoinedKeyAttribute. " +
306 "Check type " + model
.Type
.FullName
);
309 model
.Key
= new KeyModel(prop
, propAtt
);
311 else if (attribute
is VersionAttribute
)
313 VersionAttribute propAtt
= attribute
as VersionAttribute
;
316 if (model
.Version
!= null)
318 throw new ActiveRecordException("You can't specify more than one VersionAttribute. " +
319 "Check type " + model
.Type
.FullName
);
322 model
.Version
= new VersionModel(prop
, propAtt
);
324 else if (attribute
is TimestampAttribute
)
326 TimestampAttribute propAtt
= attribute
as TimestampAttribute
;
329 if (model
.Timestamp
!= null)
331 throw new ActiveRecordException("You can't specify more than one TimestampAttribute. " +
332 "Check type " + model
.Type
.FullName
);
335 model
.Timestamp
= new TimestampModel(prop
, propAtt
);
338 else if (attribute
is OneToOneAttribute
)
340 OneToOneAttribute propAtt
= attribute
as OneToOneAttribute
;
343 model
.OneToOnes
.Add(new OneToOneModel(prop
, propAtt
));
345 else if (attribute
is BelongsToAttribute
)
347 BelongsToAttribute propAtt
= attribute
as BelongsToAttribute
;
350 model
.BelongsTo
.Add(new BelongsToModel(prop
, propAtt
));
352 // The ordering is important here, HasManyToAny must comes before HasMany!
353 else if (attribute
is HasManyToAnyAttribute
)
355 HasManyToAnyAttribute propAtt
= attribute
as HasManyToAnyAttribute
;
358 hasManyToAnyModel
= new HasManyToAnyModel(prop
, propAtt
);
359 model
.HasManyToAny
.Add(hasManyToAnyModel
);
361 CollectMetaValues(hasManyToAnyModel
.MetaValues
, prop
);
363 else if (attribute
is HasManyAttribute
)
365 HasManyAttribute propAtt
= attribute
as HasManyAttribute
;
368 HasManyModel hasManyModel
= new HasManyModel(prop
, propAtt
);
369 if (propAtt
.DependentObjects
)
371 ActiveRecordModel dependentObjectModel
= new ActiveRecordModel(propAtt
.MapType
);
372 dependentObjectModel
.IsNestedType
= true;
373 dependentObjectModel
.IsNestedCompositeType
= true;
374 ProcessProperties(propAtt
.MapType
, dependentObjectModel
);
376 hasManyModel
.DependentObjectModel
= new DependentObjectModel(prop
, propAtt
, dependentObjectModel
);
378 model
.HasMany
.Add(hasManyModel
);
380 else if (attribute
is HasAndBelongsToManyAttribute
)
382 HasAndBelongsToManyAttribute propAtt
= attribute
as HasAndBelongsToManyAttribute
;
385 model
.HasAndBelongsToMany
.Add(new HasAndBelongsToManyModel(prop
, propAtt
));
387 else if (attribute
is Any
.MetaValueAttribute
)
389 if (prop
.GetCustomAttributes(typeof(HasManyToAnyAttribute
), false).Length
== 0 &&
390 prop
.GetCustomAttributes(typeof(AnyAttribute
), false).Length
== 0
392 throw new ActiveRecordException(
393 "You can't specify an Any.MetaValue without specifying the Any or HasManyToAny attribute. " +
394 "Check type " + prop
.DeclaringType
.FullName
);
396 else if (attribute
is CompositeUserTypeAttribute
)
398 CompositeUserTypeAttribute propAtt
= attribute
as CompositeUserTypeAttribute
;
401 model
.CompositeUserType
.Add(new CompositeUserTypeModel(prop
, propAtt
));
404 if (attribute
is CollectionIDAttribute
)
406 CollectionIDAttribute propAtt
= attribute
as CollectionIDAttribute
;
408 model
.CollectionIDs
.Add(new CollectionIDModel(prop
, propAtt
));
410 if (attribute
is HiloAttribute
)
412 HiloAttribute propAtt
= attribute
as HiloAttribute
;
414 model
.Hilos
.Add(new HiloModel(prop
, propAtt
));
420 model
.NotMappedProperties
.Add(prop
);
425 private static void CollectMetaValues(IList
<Any
.MetaValueAttribute
> metaStore
, PropertyInfo prop
)
427 if (metaStore
== null)
428 throw new ArgumentNullException("metaStore");
430 Any
.MetaValueAttribute
[] metaValues
=
431 prop
.GetCustomAttributes(typeof(Any
.MetaValueAttribute
), false) as Any
.MetaValueAttribute
[];
433 if (metaValues
== null || metaValues
.Length
== 0)
436 foreach (Any
.MetaValueAttribute attribute
in metaValues
)
438 metaStore
.Add(attribute
);
442 private static bool ShouldCheckBase(Type type
)
444 // Changed as suggested http://support.castleproject.org/jira/browse/AR-40
445 bool shouldCheck
= IsRootType(type
);
447 if (shouldCheck
) // Perform more checks
449 Type basetype
= type
.BaseType
;
451 while (basetype
!= typeof(object))
453 if (basetype
.IsDefined(typeof(JoinedBaseAttribute
), false)) return false;
455 object[] attrs
= basetype
.GetCustomAttributes(typeof(ActiveRecordAttribute
), false);
457 if (attrs
.Length
!= 0)
459 ActiveRecordAttribute arAttribute
= attrs
[0] as ActiveRecordAttribute
;
460 if (arAttribute
.DiscriminatorColumn
!= null)
468 basetype
= basetype
.BaseType
;
475 private static bool IsRootType(Type type
)
477 bool isRootType
= type
.BaseType
!= typeof(object) &&
478 type
.BaseType
!= typeof(ActiveRecordBase
) &&
479 type
.BaseType
!= typeof(ActiveRecordValidationBase
);
480 // && !type.BaseType.IsDefined(typeof(ActiveRecordAttribute), false);
483 if (type
.BaseType
.IsGenericType
)
485 isRootType
= type
.BaseType
.GetGenericTypeDefinition() != typeof(ActiveRecordBase
<>) &&
486 type
.BaseType
.GetGenericTypeDefinition() != typeof(ActiveRecordValidationBase
<>);
492 private static void ProcessActiveRecordAttribute(Type type
, ActiveRecordModel model
)
494 object[] attrs
= type
.GetCustomAttributes(typeof(ActiveRecordAttribute
), false);
496 if (attrs
.Length
== 0)
498 throw new ActiveRecordException(
499 String
.Format("Type {0} is not using the ActiveRecordAttribute, which is obligatory.", type
.FullName
));
502 ActiveRecordAttribute arAttribute
= attrs
[0] as ActiveRecordAttribute
;
504 PopulateActiveRecordAttribute(arAttribute
, model
);