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
.MonoRail
.ActiveRecordSupport
18 using System
.Collections
;
19 using System
.Reflection
;
20 using Castle
.ActiveRecord
;
21 using Castle
.ActiveRecord
.Framework
.Internal
;
22 using Castle
.Components
.Binder
;
23 using Iesi
.Collections
;
24 using NHibernate
.Properties
;
25 using Iesi
.Collections
.Generic
;
26 using System
.Collections
.Generic
;
29 /// Extends <see cref="DataBinder"/> class with some
30 /// ActiveRecord specific functionality.
31 /// <seealso cref="AutoLoadBehavior"/>
32 /// <seealso cref="ARDataBindAttribute"/>
35 /// Autoload can be turned <i>on</i> on the parameter, see <see cref="AutoLoadBehavior"/>.
37 public class ARDataBinder
: DataBinder
39 protected internal static readonly object[] EmptyArg
= new object[0];
41 private AutoLoadBehavior autoLoad
;
42 private bool persistchanges
;
43 private string[] expectCollPropertiesList
;
44 private Stack
<ActiveRecordModel
> modelStack
= new Stack
<ActiveRecordModel
>();
47 /// Gets or sets a value indicating if the changes should be persisted.
49 /// <value><c>true</c> if the changes should be persisted; otherwise, <c>false</c>.</value>
50 public bool PersistChanges
52 get { return persistchanges; }
53 set { persistchanges = value; }
57 /// Gets or sets the <see cref="AutoLoadBehavior"/>.
59 /// <value>The auto load behavior.</value>
60 public AutoLoadBehavior AutoLoad
62 get { return autoLoad; }
63 set { autoLoad = value; }
66 protected override void PushInstance(object instance
, string prefix
)
68 ActiveRecordModel model
= ActiveRecordModel
.GetModel(instance
.GetType());
70 if (model
== null && modelStack
.Count
!= 0)
72 foreach(NestedModel nestedModel
in CurrentARModel
.Components
)
74 if (string.Compare(nestedModel
.Property
.Name
, prefix
, true) == 0)
76 model
= nestedModel
.Model
;
84 modelStack
.Push(model
);
87 base.PushInstance(instance
, prefix
);
90 protected override void PopInstance(object instance
, string prefix
)
92 ActiveRecordModel model
= ActiveRecordModel
.GetModel(instance
.GetType());
94 if (model
== null && CurrentARModel
!= null && CurrentARModel
.IsNestedType
)
101 ActiveRecordModel actualModel
= modelStack
.Pop();
103 if (actualModel
!= model
)
105 throw new BindingException("Unexpected ARModel on the stack: found {0}, expecting {1}",
106 actualModel
.ToString(), model
.ToString());
110 base.PopInstance(instance
, prefix
);
114 /// Gets the current AR model.
116 /// <value>The current AR model.</value>
117 protected ActiveRecordModel CurrentARModel
119 get { return modelStack.Count == 0 ? null : modelStack.Peek(); }
122 public object BindObject(Type targetType
, string prefix
, string exclude
, string allow
, string expect
,
123 CompositeNode treeRoot
)
125 expectCollPropertiesList
= CreateNormalizedList(expect
);
127 return BindObject(targetType
, prefix
, exclude
, allow
, treeRoot
);
130 protected override object CreateInstance(Type instanceType
, String paramPrefix
, Node node
)
134 throw new BindingException(
135 "Nothing found for the given prefix. Are you sure the form fields are using the prefix " +
139 if (node
.NodeType
!= NodeType
.Composite
)
141 throw new BindingException("Unexpected node type. Expecting Composite, found " + node
.NodeType
);
144 CompositeNode cNode
= (CompositeNode
) node
;
148 bool shouldLoad
= autoLoad
!= AutoLoadBehavior
.Never
;
150 if (autoLoad
== AutoLoadBehavior
.OnlyNested
)
152 shouldLoad
= StackDepth
!= 0;
155 ActiveRecordModel model
= ActiveRecordModel
.GetModel(instanceType
);
157 if (shouldLoad
&& model
== null) // Nested type or unregistered type
164 if (instanceType
.IsArray
)
166 throw new BindingException("ARDataBinder AutoLoad does not support arrays");
169 PrimaryKeyModel pkModel
;
171 object id
= ObtainPrimaryKeyValue(model
, cNode
, paramPrefix
, out pkModel
);
175 instance
= FindByPrimaryKey(instanceType
, id
);
179 if (autoLoad
== AutoLoadBehavior
.NewInstanceIfInvalidKey
||
180 (autoLoad
== AutoLoadBehavior
.NewRootInstanceIfInvalidKey
&& StackDepth
== 0))
182 instance
= base.CreateInstance(instanceType
, paramPrefix
, node
);
184 else if (autoLoad
== AutoLoadBehavior
.NullIfInvalidKey
||
185 autoLoad
== AutoLoadBehavior
.OnlyNested
||
186 (autoLoad
== AutoLoadBehavior
.NewRootInstanceIfInvalidKey
&& StackDepth
!= 0))
192 throw new BindingException(string.Format(
193 "Could not find primary key '{0}' for '{1}'",
194 pkModel
.Property
.Name
, instanceType
.FullName
));
200 instance
= base.CreateInstance(instanceType
, paramPrefix
, node
);
207 /// for joined subclasses HasAndBelongsToMany properties doesn't include the ones of the parent class
208 /// so we need to check them recursively
210 protected bool FindPropertyInHasAndBelongsToMany(ActiveRecordModel model
, string propertyName
,
211 ref Type foundType
, ref ActiveRecordModel foundModel
)
213 foreach(HasAndBelongsToManyModel hasMany2ManyModel
in model
.HasAndBelongsToMany
)
215 // Inverse=true relations will be ignored
216 if (hasMany2ManyModel
.Property
.Name
== propertyName
&& !hasMany2ManyModel
.HasManyAtt
.Inverse
)
218 foundType
= hasMany2ManyModel
.HasManyAtt
.MapType
;
219 foundModel
= ActiveRecordModel
.GetModel(foundType
);
223 if (model
.IsJoinedSubClass
|| model
.IsDiscriminatorSubClass
)
225 return FindPropertyInHasAndBelongsToMany(model
.Parent
, propertyName
, ref foundType
, ref foundModel
);
231 /// for joined subclasses HasMany properties doesn't include the ones of the parent class
232 /// so we need to check them recursively
234 protected bool FindPropertyInHasMany(ActiveRecordModel model
, string propertyName
,
235 ref Type foundType
, ref ActiveRecordModel foundModel
)
237 foreach(HasManyModel hasManyModel
in model
.HasMany
)
239 // Inverse=true relations will be ignored
240 if (hasManyModel
.Property
.Name
== propertyName
&& !hasManyModel
.HasManyAtt
.Inverse
)
242 foundType
= hasManyModel
.HasManyAtt
.MapType
;
243 foundModel
= ActiveRecordModel
.GetModel(foundType
);
247 if (model
.IsJoinedSubClass
|| model
.IsDiscriminatorSubClass
)
249 return FindPropertyInHasMany(model
.Parent
, propertyName
, ref foundType
, ref foundModel
);
254 protected override object BindSpecialObjectInstance(Type instanceType
, string prefix
, Node node
,
259 ActiveRecordModel model
= CurrentARModel
;
266 object container
= CreateContainer(instanceType
);
269 Type targetType
= null;
270 ActiveRecordModel targetModel
= null;
272 found
= FindPropertyInHasAndBelongsToMany(model
, prefix
, ref targetType
, ref targetModel
);
276 found
= FindPropertyInHasMany(model
, prefix
, ref targetType
, ref targetModel
);
283 ClearContainer(container
);
285 if (node
.NodeType
== NodeType
.Indexed
)
287 IndexedNode indexNode
= (IndexedNode
) node
;
289 Array collArray
= Array
.CreateInstance(targetType
, indexNode
.ChildrenCount
);
291 collArray
= (Array
) InternalBindObject(collArray
.GetType(), prefix
, node
);
293 foreach(object item
in collArray
)
295 AddToContainer(container
, item
);
298 else if (node
.NodeType
== NodeType
.Leaf
)
300 PrimaryKeyModel pkModel
= targetModel
.PrimaryKey
;
301 Type pkType
= pkModel
.Property
.PropertyType
;
303 LeafNode leafNode
= (LeafNode
) node
;
307 if (leafNode
.IsArray
) // Multiples values found
309 foreach(object element
in (Array
) leafNode
.Value
)
311 object keyConverted
= Converter
.Convert(pkType
, leafNode
.ValueType
.GetElementType(),
312 element
, out convSucceeded
);
316 object item
= FindByPrimaryKey(targetType
, keyConverted
);
317 AddToContainer(container
, item
);
321 else // Single value found
323 object keyConverted
= Converter
.Convert(pkType
, leafNode
.ValueType
.GetElementType(),
324 leafNode
.Value
, out convSucceeded
);
328 object item
= FindByPrimaryKey(targetType
, keyConverted
);
329 AddToContainer(container
, item
);
338 protected virtual object FindByPrimaryKey(Type targetType
, object id
)
340 return FindByPrimaryKey(targetType
, id
, true);
343 protected virtual object FindByPrimaryKey(Type targetType
, object id
, bool throwOnNotFound
)
345 return ActiveRecordMediator
.FindByPrimaryKey(targetType
, id
, throwOnNotFound
);
348 protected override bool IsSpecialType(Type instanceType
)
350 return IsContainerType(instanceType
);
353 protected override void SetPropertyValue(object instance
, PropertyInfo prop
, object value)
355 object[] attributes
= prop
.GetCustomAttributes(typeof(WithAccessAttribute
), false);
357 if (attributes
.Length
== 0)
359 base.SetPropertyValue(instance
, prop
, value);
363 WithAccessAttribute accessAttribute
= (WithAccessAttribute
) attributes
[0];
364 IPropertyAccessor propertyAccessor
;
366 switch(accessAttribute
.Access
)
368 case PropertyAccess
.Property
:
369 propertyAccessor
= PropertyAccessorFactory
.GetPropertyAccessor("property");
371 case PropertyAccess
.Field
:
372 propertyAccessor
= PropertyAccessorFactory
.GetPropertyAccessor("field");
374 case PropertyAccess
.FieldCamelcase
:
375 propertyAccessor
= PropertyAccessorFactory
.GetPropertyAccessor("field.camelcase");
377 case PropertyAccess
.FieldCamelcaseUnderscore
:
378 propertyAccessor
= PropertyAccessorFactory
.GetPropertyAccessor("field.camelcase-underscore");
380 case PropertyAccess
.FieldPascalcaseMUnderscore
:
381 propertyAccessor
= PropertyAccessorFactory
.GetPropertyAccessor("field.pascalcase-m-underscore");
383 case PropertyAccess
.FieldLowercaseUnderscore
:
384 propertyAccessor
= PropertyAccessorFactory
.GetPropertyAccessor("field.lowercase-underscore");
386 case PropertyAccess
.NosetterCamelcase
:
387 propertyAccessor
= PropertyAccessorFactory
.GetPropertyAccessor("nosetter.camelcase");
389 case PropertyAccess
.NosetterCamelcaseUnderscore
:
390 propertyAccessor
= PropertyAccessorFactory
.GetPropertyAccessor("nosetter.camelcase-underscore");
392 case PropertyAccess
.NosetterPascalcaseMUndersc
:
393 propertyAccessor
= PropertyAccessorFactory
.GetPropertyAccessor("nosetter.pascalcase-m-underscore");
395 case PropertyAccess
.NosetterLowercaseUnderscore
:
396 propertyAccessor
= PropertyAccessorFactory
.GetPropertyAccessor("nosetter.lowercase-underscore");
398 case PropertyAccess
.NosetterLowercase
:
399 propertyAccessor
= PropertyAccessorFactory
.GetPropertyAccessor("nosetter.lowercase");
402 throw new ArgumentOutOfRangeException();
405 propertyAccessor
.GetSetter(instance
.GetType(), prop
.Name
).Set(instance
, value);
409 /// for joined subclasses BelongsTo properties doesn't include the ones of the parent class
410 /// so we need to check them recursively
412 protected bool IsBelongsToRef(ActiveRecordModel arModel
, string prefix
)
414 foreach(BelongsToModel model
in arModel
.BelongsTo
)
416 if (model
.Property
.Name
== prefix
)
421 if (arModel
.IsJoinedSubClass
|| arModel
.IsDiscriminatorSubClass
)
423 return IsBelongsToRef(arModel
.Parent
, prefix
);
428 protected override bool ShouldRecreateInstance(object value, Type type
, string prefix
, Node node
)
430 if (IsContainerType(type
))
435 if (node
!= null && CurrentARModel
!= null)
437 // If it's a belongsTo ref, we need to recreate it
438 // instead of overwrite its properties, otherwise NHibernate will complain
439 if (IsBelongsToRef(CurrentARModel
, prefix
))
445 return base.ShouldRecreateInstance(value, type
, prefix
, node
);
448 protected override void BeforeBindingProperty(object instance
, PropertyInfo prop
, string prefix
,
451 base.BeforeBindingProperty(instance
, prop
, prefix
, node
);
453 if (IsPropertyExpected(prop
, node
))
455 ClearExpectedCollectionProperties(instance
, prop
);
459 private bool IsPropertyExpected(PropertyInfo prop
, CompositeNode node
)
461 string propId
= string.Format("{0}.{1}", node
.FullName
, prop
.Name
);
463 if (expectCollPropertiesList
!= null)
465 return Array
.BinarySearch(expectCollPropertiesList
, propId
, CaseInsensitiveComparer
.Default
) >= 0;
471 private void ClearExpectedCollectionProperties(object instance
, PropertyInfo prop
)
473 object value = prop
.GetValue(instance
, null);
475 ClearContainer(value);
480 private object ObtainPrimaryKeyValue(ActiveRecordModel model
, CompositeNode node
, String prefix
,
481 out PrimaryKeyModel pkModel
)
483 pkModel
= ObtainPrimaryKey(model
);
485 String pkPropName
= pkModel
.Property
.Name
;
487 Node idNode
= node
.GetChildNode(pkPropName
);
489 if (idNode
== null) return null;
491 if (idNode
!= null && idNode
.NodeType
!= NodeType
.Leaf
)
493 throw new BindingException("Expecting leaf node to contain id for ActiveRecord class. " +
494 "Prefix: {0} PK Property Name: {1}", prefix
, pkPropName
);
497 LeafNode lNode
= (LeafNode
) idNode
;
501 throw new BindingException("ARDataBinder autoload failed as element {0} " +
502 "doesn't have a primary key {1} value", prefix
, pkPropName
);
507 return Converter
.Convert(pkModel
.Property
.PropertyType
, lNode
.ValueType
, lNode
.Value
, out conversionSuc
);
510 private static PrimaryKeyModel
ObtainPrimaryKey(ActiveRecordModel model
)
512 if (model
.IsJoinedSubClass
|| model
.IsDiscriminatorSubClass
)
514 return ObtainPrimaryKey(model
.Parent
);
516 return model
.PrimaryKey
;
519 private bool IsValidKey(object id
)
523 if (id
.GetType() == typeof(String
))
525 return id
.ToString() != String
.Empty
;
527 else if (id
.GetType() == typeof(Guid
))
529 return Guid
.Empty
!= ((Guid
) id
);
533 return Convert
.ToInt64(id
) != 0;
540 private bool IsContainerType(Type type
)
542 bool isContainerType
= type
== typeof(IList
) || type
== typeof(ISet
);
544 if (!isContainerType
&& type
.IsGenericType
)
546 Type
[] genericArgs
= type
.GetGenericArguments();
548 Type genType
= typeof(ICollection
<>).MakeGenericType(genericArgs
);
550 isContainerType
= genType
.IsAssignableFrom(type
);
553 return isContainerType
;
556 private object CreateContainer(Type type
)
558 if (type
.IsGenericType
)
560 if (type
.GetGenericTypeDefinition() == typeof(ISet
<>))
562 Type
[] genericArgs
= type
.GetGenericArguments();
563 Type genericType
= typeof(HashedSet
<>).MakeGenericType(genericArgs
);
564 return Activator
.CreateInstance(genericType
);
566 else if (type
.GetGenericTypeDefinition() == typeof(IList
<>))
568 Type
[] genericArgs
= type
.GetGenericArguments();
569 Type genericType
= typeof(List
<>).MakeGenericType(genericArgs
);
570 return Activator
.CreateInstance(genericType
);
575 if (type
== typeof(IList
))
577 return new ArrayList();
579 else if (type
== typeof(ISet
))
581 return new HashedSet();
587 private void ClearContainer(object instance
)
589 if (instance
is IList
)
591 (instance
as IList
).Clear();
593 else if (instance
is ISet
)
595 (instance
as ISet
).Clear();
599 private void AddToContainer(object container
, object item
)
601 if (container
is IList
)
603 (container
as IList
).Add(item
);
605 else if (container
is ISet
)
607 (container
as ISet
).Add(item
);
609 else if (container
!= null)
611 Type itemType
= item
.GetType();
613 Type collectionType
= typeof(ICollection
<>).MakeGenericType(itemType
);
615 if (collectionType
.IsAssignableFrom(container
.GetType()))
617 MethodInfo addMethod
= container
.GetType().GetMethod("Add");
619 addMethod
.Invoke(container
, new object[] {item}
);