1 // Copyright 2004-2007 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
;
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 model
.PrimaryKey
= new PrimaryKeyModel(prop
, propAtt
);
228 else if (attribute
is CompositeKeyAttribute
)
230 CompositeKeyAttribute propAtt
= attribute
as CompositeKeyAttribute
;
233 model
.CompositeKey
= new CompositeKeyModel(prop
, propAtt
);
235 else if (attribute
is AnyAttribute
)
237 AnyAttribute anyAtt
= attribute
as AnyAttribute
;
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
;
249 model
.Properties
.Add(new PropertyModel(prop
, propAtt
));
251 else if (attribute
is NestedAttribute
)
253 NestedAttribute propAtt
= attribute
as NestedAttribute
;
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
;
275 model
.ComponentParent
.Add(new NestedParentReferenceModel(prop
, nestedParentAtt
));
277 else if (attribute
is JoinedKeyAttribute
)
279 JoinedKeyAttribute propAtt
= attribute
as JoinedKeyAttribute
;
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
;
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
;
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
);
317 else if (attribute
is OneToOneAttribute
)
319 OneToOneAttribute propAtt
= attribute
as OneToOneAttribute
;
322 model
.OneToOnes
.Add(new OneToOneModel(prop
, propAtt
));
324 else if (attribute
is BelongsToAttribute
)
326 BelongsToAttribute propAtt
= attribute
as BelongsToAttribute
;
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
;
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
;
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
;
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
;
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
));
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)
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)
447 basetype
= basetype
.BaseType
;
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);
462 if (type
.BaseType
.IsGenericType
)
464 isRootType
= type
.BaseType
.GetGenericTypeDefinition() != typeof(ActiveRecordBase
<>) &&
465 type
.BaseType
.GetGenericTypeDefinition() != typeof(ActiveRecordValidationBase
<>);
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
);