Removed untyped contructor from ComponentRegistration and add a protected setter.
[castle.git] / MonoRail / Castle.MonoRail.ActiveRecordSupport / ARDataBinder.cs
blob2acb013ed2b82d3a1016b013c3923c25be63e534
1 // Copyright 2004-2008 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.MonoRail.ActiveRecordSupport
17 using System;
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;
28 /// <summary>
29 /// Extends <see cref="DataBinder"/> class with some
30 /// ActiveRecord specific functionality.
31 /// <seealso cref="AutoLoadBehavior"/>
32 /// <seealso cref="ARDataBindAttribute"/>
33 /// </summary>
34 /// <remarks>
35 /// Autoload can be turned <i>on</i> on the parameter, see <see cref="AutoLoadBehavior"/>.
36 /// </remarks>
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>();
46 /// <summary>
47 /// Gets or sets a value indicating if the changes should be persisted.
48 /// </summary>
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; }
56 /// <summary>
57 /// Gets or sets the <see cref="AutoLoadBehavior"/>.
58 /// </summary>
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;
77 break;
82 if (model != null)
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)
96 modelStack.Pop();
99 if (model != null)
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);
113 /// <summary>
114 /// Gets the current AR model.
115 /// </summary>
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)
132 if (node == null)
134 throw new BindingException(
135 "Nothing found for the given prefix. Are you sure the form fields are using the prefix " +
136 paramPrefix + "?");
139 if (node.NodeType != NodeType.Composite)
141 throw new BindingException("Unexpected node type. Expecting Composite, found " + node.NodeType);
144 CompositeNode cNode = (CompositeNode) node;
146 object instance;
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
159 shouldLoad = false;
162 if (shouldLoad)
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);
173 if (IsValidKey(id))
175 instance = FindByPrimaryKey(instanceType, id);
177 else
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))
188 instance = null;
190 else
192 throw new BindingException(string.Format(
193 "Could not find primary key '{0}' for '{1}'",
194 pkModel.Property.Name, instanceType.FullName));
198 else
200 instance = base.CreateInstance(instanceType, paramPrefix, node);
203 return instance;
206 /// <summary>
207 /// for joined subclasses HasAndBelongsToMany properties doesn't include the ones of the parent class
208 /// so we need to check them recursively
209 /// </summary>
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);
220 return true;
223 if (model.IsJoinedSubClass || model.IsDiscriminatorSubClass)
225 return FindPropertyInHasAndBelongsToMany(model.Parent, propertyName, ref foundType, ref foundModel);
227 return false;
230 /// <summary>
231 /// for joined subclasses HasMany properties doesn't include the ones of the parent class
232 /// so we need to check them recursively
233 /// </summary>
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);
244 return true;
247 if (model.IsJoinedSubClass || model.IsDiscriminatorSubClass)
249 return FindPropertyInHasMany(model.Parent, propertyName, ref foundType, ref foundModel);
251 return false;
254 protected override object BindSpecialObjectInstance(Type instanceType, string prefix, Node node,
255 out bool succeeded)
257 succeeded = false;
259 ActiveRecordModel model = CurrentARModel;
261 if (model == null)
263 return null;
266 object container = CreateContainer(instanceType);
268 bool found = false;
269 Type targetType = null;
270 ActiveRecordModel targetModel = null;
272 found = FindPropertyInHasAndBelongsToMany(model, prefix, ref targetType, ref targetModel);
274 if (!found)
276 found = FindPropertyInHasMany(model, prefix, ref targetType, ref targetModel);
279 if (found)
281 succeeded = true;
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;
305 bool convSucceeded;
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);
314 if (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);
326 if (convSucceeded)
328 object item = FindByPrimaryKey(targetType, keyConverted);
329 AddToContainer(container, item);
335 return container;
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);
360 return;
363 WithAccessAttribute accessAttribute = (WithAccessAttribute) attributes[0];
364 IPropertyAccessor propertyAccessor;
366 switch(accessAttribute.Access)
368 case PropertyAccess.Property:
369 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("property");
370 break;
371 case PropertyAccess.Field:
372 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("field");
373 break;
374 case PropertyAccess.FieldCamelcase:
375 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("field.camelcase");
376 break;
377 case PropertyAccess.FieldCamelcaseUnderscore:
378 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("field.camelcase-underscore");
379 break;
380 case PropertyAccess.FieldPascalcaseMUnderscore:
381 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("field.pascalcase-m-underscore");
382 break;
383 case PropertyAccess.FieldLowercaseUnderscore:
384 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("field.lowercase-underscore");
385 break;
386 case PropertyAccess.NosetterCamelcase:
387 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("nosetter.camelcase");
388 break;
389 case PropertyAccess.NosetterCamelcaseUnderscore:
390 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("nosetter.camelcase-underscore");
391 break;
392 case PropertyAccess.NosetterPascalcaseMUndersc:
393 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("nosetter.pascalcase-m-underscore");
394 break;
395 case PropertyAccess.NosetterLowercaseUnderscore:
396 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("nosetter.lowercase-underscore");
397 break;
398 case PropertyAccess.NosetterLowercase:
399 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("nosetter.lowercase");
400 break;
401 default:
402 throw new ArgumentOutOfRangeException();
405 propertyAccessor.GetSetter(instance.GetType(), prop.Name).Set(instance, value);
408 /// <summary>
409 /// for joined subclasses BelongsTo properties doesn't include the ones of the parent class
410 /// so we need to check them recursively
411 /// </summary>
412 protected bool IsBelongsToRef(ActiveRecordModel arModel, string prefix)
414 foreach(BelongsToModel model in arModel.BelongsTo)
416 if (model.Property.Name == prefix)
418 return true;
421 if (arModel.IsJoinedSubClass || arModel.IsDiscriminatorSubClass)
423 return IsBelongsToRef(arModel.Parent, prefix);
425 return false;
428 protected override bool ShouldRecreateInstance(object value, Type type, string prefix, Node node)
430 if (IsContainerType(type))
432 return true;
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))
441 return true;
445 return base.ShouldRecreateInstance(value, type, prefix, node);
448 protected override void BeforeBindingProperty(object instance, PropertyInfo prop, string prefix,
449 CompositeNode node)
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;
468 return false;
471 private void ClearExpectedCollectionProperties(object instance, PropertyInfo prop)
473 object value = prop.GetValue(instance, null);
475 ClearContainer(value);
478 #region helpers
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;
499 if (lNode == null)
501 throw new BindingException("ARDataBinder autoload failed as element {0} " +
502 "doesn't have a primary key {1} value", prefix, pkPropName);
505 bool conversionSuc;
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)
521 if (id != null)
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);
531 else
533 return Convert.ToInt64(id) != 0;
537 return false;
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);
573 else
575 if (type == typeof(IList))
577 return new ArrayList();
579 else if (type == typeof(ISet))
581 return new HashedSet();
584 return null;
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});
624 #endregion