Fix the build.
[castle.git] / MonoRail / Castle.MonoRail.ActiveRecordSupport / ARDataBinder.cs
blobc38954fcce5eb7894a38c9c463f9cdb1a5bb8cf4
1 // Copyright 2004-2007 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.Property;
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
121 return modelStack.Count == 0 ? null : modelStack.Peek();
125 public object BindObject(Type targetType, string prefix, string exclude, string allow, string expect,
126 CompositeNode treeRoot)
128 expectCollPropertiesList = CreateNormalizedList(expect);
130 return BindObject(targetType, prefix, exclude, allow, treeRoot);
133 protected override object CreateInstance(Type instanceType, String paramPrefix, Node node)
135 if (node == null)
137 throw new BindingException(
138 "Nothing found for the given prefix. Are you sure the form fields are using the prefix " +
139 paramPrefix + "?");
142 if (node.NodeType != NodeType.Composite)
144 throw new BindingException("Unexpected node type. Expecting Composite, found " + node.NodeType);
147 CompositeNode cNode = (CompositeNode) node;
149 object instance;
151 bool shouldLoad = autoLoad != AutoLoadBehavior.Never;
153 if (autoLoad == AutoLoadBehavior.OnlyNested)
155 shouldLoad = StackDepth != 0;
158 if (shouldLoad)
160 if (instanceType.IsArray)
162 throw new BindingException("ARDataBinder AutoLoad does not support arrays");
165 ActiveRecordModel model = ActiveRecordModel.GetModel(instanceType);
167 PrimaryKeyModel pkModel;
169 object id = ObtainPrimaryKeyValue(model, cNode, paramPrefix, out pkModel);
171 if (IsValidKey(id))
173 instance = FindByPrimaryKey(instanceType, id);
175 else
177 if (autoLoad == AutoLoadBehavior.NewInstanceIfInvalidKey ||
178 (autoLoad == AutoLoadBehavior.NewRootInstanceIfInvalidKey && StackDepth == 0))
180 instance = base.CreateInstance(instanceType, paramPrefix, node);
182 else if (autoLoad == AutoLoadBehavior.NullIfInvalidKey ||
183 autoLoad == AutoLoadBehavior.OnlyNested ||
184 (autoLoad == AutoLoadBehavior.NewRootInstanceIfInvalidKey && StackDepth != 0))
186 instance = null;
188 else
190 throw new BindingException(string.Format(
191 "Could not find primary key '{0}' for '{1}'",
192 pkModel.Property.Name, instanceType.FullName));
196 else
198 instance = base.CreateInstance(instanceType, paramPrefix, node);
201 return instance;
204 /// <summary>
205 /// for joined subclasses HasAndBelongsToMany properties doesn't include the ones of the parent class
206 /// so we need to check them recursively
207 /// </summary>
208 protected bool FindPropertyInHasAndBelongsToMany(ActiveRecordModel model, string propertyName,
209 ref Type foundType, ref ActiveRecordModel foundModel)
211 foreach(HasAndBelongsToManyModel hasMany2ManyModel in model.HasAndBelongsToMany)
213 // Inverse=true relations will be ignored
214 if (hasMany2ManyModel.Property.Name == propertyName && !hasMany2ManyModel.HasManyAtt.Inverse)
216 foundType = hasMany2ManyModel.HasManyAtt.MapType;
217 foundModel = ActiveRecordModel.GetModel(foundType);
218 return true;
221 if (model.IsJoinedSubClass || model.IsDiscriminatorSubClass)
223 return FindPropertyInHasAndBelongsToMany(model.Parent, propertyName, ref foundType, ref foundModel);
225 return false;
228 /// <summary>
229 /// for joined subclasses HasMany properties doesn't include the ones of the parent class
230 /// so we need to check them recursively
231 /// </summary>
232 protected bool FindPropertyInHasMany(ActiveRecordModel model, string propertyName,
233 ref Type foundType, ref ActiveRecordModel foundModel)
235 foreach(HasManyModel hasManyModel in model.HasMany)
237 // Inverse=true relations will be ignored
238 if (hasManyModel.Property.Name == propertyName && !hasManyModel.HasManyAtt.Inverse)
240 foundType = hasManyModel.HasManyAtt.MapType;
241 foundModel = ActiveRecordModel.GetModel(foundType);
242 return true;
245 if (model.IsJoinedSubClass || model.IsDiscriminatorSubClass)
247 return FindPropertyInHasMany(model.Parent, propertyName, ref foundType, ref foundModel);
249 return false;
252 protected override object BindSpecialObjectInstance(Type instanceType, string prefix, Node node,
253 out bool succeeded)
255 succeeded = false;
257 ActiveRecordModel model = CurrentARModel;
259 if (model == null)
261 return null;
264 object container = CreateContainer(instanceType);
266 bool found = false;
267 Type targetType = null;
268 ActiveRecordModel targetModel = null;
270 found = FindPropertyInHasAndBelongsToMany(model, prefix, ref targetType, ref targetModel);
272 if (!found)
274 found = FindPropertyInHasMany(model, prefix, ref targetType, ref targetModel);
277 if (found)
279 succeeded = true;
281 ClearContainer(container);
283 if (node.NodeType == NodeType.Indexed)
285 IndexedNode indexNode = (IndexedNode) node;
287 Array collArray = Array.CreateInstance(targetType, indexNode.ChildrenCount);
289 collArray = (Array) InternalBindObject(collArray.GetType(), prefix, node);
291 foreach(object item in collArray)
293 AddToContainer(container, item);
296 else if (node.NodeType == NodeType.Leaf)
298 PrimaryKeyModel pkModel = targetModel.PrimaryKey;
299 Type pkType = pkModel.Property.PropertyType;
301 LeafNode leafNode = (LeafNode) node;
303 bool convSucceeded;
305 if (leafNode.IsArray) // Multiples values found
307 foreach(object element in (Array) leafNode.Value)
309 object keyConverted = Converter.Convert(pkType, leafNode.ValueType.GetElementType(),
310 element, out convSucceeded);
312 if (convSucceeded)
314 object item = FindByPrimaryKey(targetType, keyConverted);
315 AddToContainer(container, item);
319 else // Single value found
321 object keyConverted = Converter.Convert(pkType, leafNode.ValueType.GetElementType(),
322 leafNode.Value, out convSucceeded);
324 if (convSucceeded)
326 object item = FindByPrimaryKey(targetType, keyConverted);
327 AddToContainer(container, item);
333 return container;
336 protected virtual object FindByPrimaryKey(Type targetType, object id)
338 return FindByPrimaryKey(targetType, id, true);
341 protected virtual object FindByPrimaryKey(Type targetType, object id, bool throwOnNotFound)
343 return ActiveRecordMediator.FindByPrimaryKey(targetType, id, throwOnNotFound);
346 protected override bool IsSpecialType(Type instanceType)
348 return IsContainerType(instanceType);
351 protected override void SetPropertyValue(object instance, PropertyInfo prop, object value)
353 object[] attributes = prop.GetCustomAttributes(typeof(WithAccessAttribute), false);
355 if (attributes.Length == 0)
357 base.SetPropertyValue(instance, prop, value);
358 return;
361 WithAccessAttribute accessAttribute = (WithAccessAttribute) attributes[0];
362 IPropertyAccessor propertyAccessor;
364 switch(accessAttribute.Access)
366 case PropertyAccess.Property:
367 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("property");
368 break;
369 case PropertyAccess.Field:
370 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("field");
371 break;
372 case PropertyAccess.FieldCamelcase:
373 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("field.camelcase");
374 break;
375 case PropertyAccess.FieldCamelcaseUnderscore:
376 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("field.camelcase-underscore");
377 break;
378 case PropertyAccess.FieldPascalcaseMUnderscore:
379 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("field.pascalcase-m-underscore");
380 break;
381 case PropertyAccess.FieldLowercaseUnderscore:
382 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("field.lowercase-underscore");
383 break;
384 case PropertyAccess.NosetterCamelcase:
385 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("nosetter.camelcase");
386 break;
387 case PropertyAccess.NosetterCamelcaseUnderscore:
388 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("nosetter.camelcase-underscore");
389 break;
390 case PropertyAccess.NosetterPascalcaseMUndersc:
391 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("nosetter.pascalcase-m-underscore");
392 break;
393 case PropertyAccess.NosetterLowercaseUnderscore:
394 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("nosetter.lowercase-underscore");
395 break;
396 case PropertyAccess.NosetterLowercase:
397 propertyAccessor = PropertyAccessorFactory.GetPropertyAccessor("nosetter.lowercase");
398 break;
399 default:
400 throw new ArgumentOutOfRangeException();
403 propertyAccessor.GetSetter(instance.GetType(), prop.Name).Set(instance, value);
406 /// <summary>
407 /// for joined subclasses BelongsTo properties doesn't include the ones of the parent class
408 /// so we need to check them recursively
409 /// </summary>
410 protected bool IsBelongsToRef(ActiveRecordModel arModel, string prefix)
412 foreach(BelongsToModel model in arModel.BelongsTo)
414 if (model.Property.Name == prefix)
416 return true;
419 if (arModel.IsJoinedSubClass || arModel.IsDiscriminatorSubClass)
421 return IsBelongsToRef(arModel.Parent, prefix);
423 return false;
426 protected override bool ShouldRecreateInstance(object value, Type type, string prefix, Node node)
428 if (IsContainerType(type))
430 return true;
433 if (node != null && CurrentARModel != null)
435 // If it's a belongsTo ref, we need to recreate it
436 // instead of overwrite its properties, otherwise NHibernate will complain
437 if (IsBelongsToRef(CurrentARModel, prefix))
439 return true;
443 return base.ShouldRecreateInstance(value, type, prefix, node);
446 protected override void BeforeBindingProperty(object instance, PropertyInfo prop, string prefix,
447 CompositeNode node)
449 base.BeforeBindingProperty(instance, prop, prefix, node);
451 if (IsPropertyExpected(prop, node))
453 ClearExpectedCollectionProperties(instance, prop);
457 private bool IsPropertyExpected(PropertyInfo prop, CompositeNode node)
459 string propId = string.Format("{0}.{1}", node.FullName, prop.Name);
461 if (expectCollPropertiesList != null)
463 return Array.BinarySearch(expectCollPropertiesList, propId, CaseInsensitiveComparer.Default) >= 0;
466 return false;
469 private void ClearExpectedCollectionProperties(object instance, PropertyInfo prop)
471 object value = prop.GetValue(instance, null);
473 ClearContainer(value);
476 #region helpers
478 private object ObtainPrimaryKeyValue(ActiveRecordModel model, CompositeNode node, String prefix,
479 out PrimaryKeyModel pkModel)
481 pkModel = ObtainPrimaryKey(model);
483 String pkPropName = pkModel.Property.Name;
485 Node idNode = node.GetChildNode(pkPropName);
487 if (idNode != null && idNode.NodeType != NodeType.Leaf)
489 throw new BindingException("Expecting leaf node to contain id for ActiveRecord class. " +
490 "Prefix: {0} PK Property Name: {1}", prefix, pkPropName);
493 LeafNode lNode = (LeafNode) idNode;
495 if (lNode == null)
497 throw new BindingException("ARDataBinder autoload failed as element {0} " +
498 "doesn't have a primary key {1} value", prefix, pkPropName);
501 bool conversionSuc;
503 return Converter.Convert(pkModel.Property.PropertyType, lNode.ValueType, lNode.Value, out conversionSuc);
506 private static PrimaryKeyModel ObtainPrimaryKey(ActiveRecordModel model)
508 if (model.IsJoinedSubClass || model.IsDiscriminatorSubClass)
510 return ObtainPrimaryKey(model.Parent);
512 return model.PrimaryKey;
515 private bool IsValidKey(object id)
517 if (id != null)
519 if (id.GetType() == typeof(String))
521 return id.ToString() != String.Empty;
523 else if (id.GetType() == typeof(Guid))
525 return Guid.Empty != ((Guid) id);
527 else
529 return Convert.ToInt64(id) != 0;
533 return false;
536 private bool IsContainerType(Type type)
538 bool isContainerType = type == typeof(IList) || type == typeof(ISet);
540 if (!isContainerType && type.IsGenericType)
542 Type[] genericArgs = type.GetGenericArguments();
544 Type genType = typeof(ICollection<>).MakeGenericType(genericArgs);
546 isContainerType = genType.IsAssignableFrom(type);
549 return isContainerType;
552 private object CreateContainer(Type type)
554 if (type.IsGenericType)
556 if (type.GetGenericTypeDefinition() == typeof(ISet<>))
558 Type[] genericArgs = type.GetGenericArguments();
559 Type genericType = typeof(HashedSet<>).MakeGenericType(genericArgs);
560 return Activator.CreateInstance(genericType);
562 else if (type.GetGenericTypeDefinition() == typeof(IList<>))
564 Type[] genericArgs = type.GetGenericArguments();
565 Type genericType = typeof(List<>).MakeGenericType(genericArgs);
566 return Activator.CreateInstance(genericType);
569 else
571 if (type == typeof(IList))
573 return new ArrayList();
575 else if (type == typeof(ISet))
577 return new HashedSet();
580 return null;
583 private void ClearContainer(object instance)
585 if (instance is IList)
587 (instance as IList).Clear();
589 else if (instance is ISet)
591 (instance as ISet).Clear();
595 private void AddToContainer(object container, object item)
597 if (container is IList)
599 (container as IList).Add(item);
601 else if (container is ISet)
603 (container as ISet).Add(item);
605 else if (container != null)
607 Type itemType = item.GetType();
609 Type collectionType = typeof(ICollection<>).MakeGenericType(itemType);
611 if (collectionType.IsAssignableFrom(container.GetType()))
613 MethodInfo addMethod = container.GetType().GetMethod("Add");
615 addMethod.Invoke(container, new object[] {item});
620 #endregion