diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2008-09-20 19:18:00 -0700 |
---|---|---|
committer | Andrew <andrewarnott@gmail.com> | 2008-09-20 19:18:00 -0700 |
commit | f7fa44d6001b0c104cbf209cd7455e536e02cbe9 (patch) | |
tree | f6739f0963a031b3d7155719a7dd302bab383666 /src/DotNetOAuth/Messaging/Reflection | |
parent | c9304587876374c2866f44135cf24372f432a77d (diff) | |
parent | f7326ec97ead94425cf8b22d0aea5f7bf67ebc8f (diff) | |
download | DotNetOpenAuth-f7fa44d6001b0c104cbf209cd7455e536e02cbe9.zip DotNetOpenAuth-f7fa44d6001b0c104cbf209cd7455e536e02cbe9.tar.gz DotNetOpenAuth-f7fa44d6001b0c104cbf209cd7455e536e02cbe9.tar.bz2 |
Merge branch 'sersync'
Diffstat (limited to 'src/DotNetOAuth/Messaging/Reflection')
5 files changed, 505 insertions, 0 deletions
diff --git a/src/DotNetOAuth/Messaging/Reflection/MessageDescription.cs b/src/DotNetOAuth/Messaging/Reflection/MessageDescription.cs new file mode 100644 index 0000000..9442f15 --- /dev/null +++ b/src/DotNetOAuth/Messaging/Reflection/MessageDescription.cs @@ -0,0 +1,91 @@ +//-----------------------------------------------------------------------
+// <copyright file="MessageDescription.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging.Reflection {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Reflection;
+ using System.Globalization;
+ using System.Diagnostics;
+
+ internal class MessageDescription {
+ private static Dictionary<Type, MessageDescription> reflectedMessageTypes = new Dictionary<Type,MessageDescription>();
+ private Type messageType;
+ private Dictionary<string, MessagePart> mapping;
+
+ private MessageDescription(Type messageType) {
+ Debug.Assert(messageType != null, "messageType == null");
+
+ if (!typeof(IProtocolMessage).IsAssignableFrom(messageType)) {
+ throw new ArgumentException(string.Format(
+ CultureInfo.CurrentCulture,
+ MessagingStrings.UnexpectedType,
+ typeof(IProtocolMessage),
+ messageType));
+ }
+
+ this.messageType = messageType;
+ this.ReflectMessageType();
+ }
+
+ internal static MessageDescription Get(Type messageType) {
+ if (messageType == null) {
+ throw new ArgumentNullException("messageType");
+ }
+
+ MessageDescription result;
+ if (!reflectedMessageTypes.TryGetValue(messageType, out result)) {
+ lock (reflectedMessageTypes) {
+ if (!reflectedMessageTypes.TryGetValue(messageType, out result)) {
+ reflectedMessageTypes[messageType] = result = new MessageDescription(messageType);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ internal IDictionary<string, MessagePart> Mapping {
+ get { return this.mapping; }
+ }
+
+ internal void ReflectMessageType() {
+ this.mapping = new Dictionary<string, MessagePart>();
+
+ Type currentType = this.messageType;
+ do {
+ foreach (MemberInfo member in currentType.GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)) {
+ if (member is PropertyInfo || member is FieldInfo) {
+ MessagePartAttribute partAttribute = member.GetCustomAttributes(typeof(MessagePartAttribute), true).OfType<MessagePartAttribute>().FirstOrDefault();
+ if (partAttribute != null) {
+ MessagePart part = new MessagePart(member, partAttribute);
+ this.mapping.Add(part.Name, part);
+ }
+ }
+ }
+ currentType = currentType.BaseType;
+ } while (currentType != null);
+ }
+
+ /// <summary>
+ /// Verifies that a given set of keys include all the required parameters
+ /// for this message type or throws an exception.
+ /// </summary>
+ /// <exception cref="ProtocolException">Thrown when required parts of a message are not in <paramref name="keys"/></exception>
+ internal void EnsureRequiredMessagePartsArePresent(IEnumerable<string> keys) {
+ var missingKeys = (from part in Mapping.Values
+ where part.IsRequired && !keys.Contains(part.Name)
+ select part.Name).ToArray();
+ if (missingKeys.Length > 0) {
+ throw new ProtocolException(string.Format(CultureInfo.CurrentCulture,
+ MessagingStrings.RequiredParametersMissing,
+ this.messageType.FullName,
+ string.Join(", ", missingKeys)));
+ }
+ }
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/Reflection/MessageDictionary.cs b/src/DotNetOAuth/Messaging/Reflection/MessageDictionary.cs new file mode 100644 index 0000000..196af54 --- /dev/null +++ b/src/DotNetOAuth/Messaging/Reflection/MessageDictionary.cs @@ -0,0 +1,206 @@ +//-----------------------------------------------------------------------
+// <copyright file="MessageDictionary.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging.Reflection {
+ using System;
+ using System.Collections;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+
+ /// <summary>
+ /// Wraps an <see cref="IProtocolMessage"/> instance in a dictionary that
+ /// provides access to both well-defined message properties and "extra"
+ /// name/value pairs that have no properties associated with them.
+ /// </summary>
+ internal class MessageDictionary : IDictionary<string, string> {
+ private IProtocolMessage message;
+
+ private MessageDescription description;
+
+ internal MessageDictionary(IProtocolMessage message) {
+ if (message == null) {
+ throw new ArgumentNullException("message");
+ }
+
+ this.message = message;
+ this.description = MessageDescription.Get(message.GetType());
+ }
+
+ #region IDictionary<string,string> Members
+
+ public void Add(string key, string value) {
+ if (value == null) {
+ throw new ArgumentNullException("value");
+ }
+
+ MessagePart part;
+ if (this.description.Mapping.TryGetValue(key, out part)) {
+ if (part.IsNondefaultValueSet(this.message)) {
+ throw new ArgumentException(MessagingStrings.KeyAlreadyExists);
+ }
+ part.SetValue(this.message, value);
+ } else {
+ this.message.ExtraData.Add(key, value);
+ }
+ }
+
+ public bool ContainsKey(string key) {
+ return this.message.ExtraData.ContainsKey(key) ||
+ (this.description.Mapping.ContainsKey(key) && this.description.Mapping[key].GetValue(this.message) != null);
+ }
+
+ public ICollection<string> Keys {
+ get {
+ List<string> keys = new List<string>(this.message.ExtraData.Count + this.description.Mapping.Count);
+ foreach (var pair in this.description.Mapping) {
+ // Don't include keys with null values, but default values for structs is ok
+ if (pair.Value.GetValue(this.message) != null) {
+ keys.Add(pair.Key);
+ }
+ }
+
+ foreach (string key in this.message.ExtraData.Keys) {
+ keys.Add(key);
+ }
+
+ return keys.AsReadOnly();
+ }
+ }
+
+ public bool Remove(string key) {
+ if (this.message.ExtraData.Remove(key)) {
+ return true;
+ } else {
+ MessagePart part;
+ if (this.description.Mapping.TryGetValue(key, out part)) {
+ if (part.GetValue(this.message) != null) {
+ part.SetValue(this.message, null);
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ public bool TryGetValue(string key, out string value) {
+ MessagePart part;
+ if (this.description.Mapping.TryGetValue(key, out part)) {
+ value = part.GetValue(this.message);
+ return true;
+ }
+ return this.message.ExtraData.TryGetValue(key, out value);
+ }
+
+ public ICollection<string> Values {
+ get {
+ List<string> values = new List<string>(this.message.ExtraData.Count + this.description.Mapping.Count);
+ foreach (MessagePart part in this.description.Mapping.Values) {
+ if (part.GetValue(this.message) != null) {
+ values.Add(part.GetValue(this.message));
+ }
+ }
+
+ foreach (string value in this.message.ExtraData.Values) {
+ Debug.Assert(value != null, "Null values should never be allowed in the extra data dictionary.");
+ values.Add(value);
+ }
+
+ return values.AsReadOnly();
+ }
+ }
+
+ public string this[string key] {
+ get {
+ MessagePart part;
+ if (this.description.Mapping.TryGetValue(key, out part)) {
+ // Never throw KeyNotFoundException for declared properties.
+ return part.GetValue(this.message);
+ } else {
+ return this.message.ExtraData[key];
+ }
+ }
+
+ set {
+ MessagePart part;
+ if (this.description.Mapping.TryGetValue(key, out part)) {
+ part.SetValue(this.message, value);
+ } else {
+ if (value == null) {
+ this.message.ExtraData.Remove(key);
+ } else {
+ this.message.ExtraData[key] = value;
+ }
+ }
+ }
+ }
+
+ #endregion
+
+ #region ICollection<KeyValuePair<string,string>> Members
+
+ public void Add(KeyValuePair<string, string> item) {
+ this.Add(item.Key, item.Value);
+ }
+
+ public void Clear() {
+ foreach (string key in this.Keys) {
+ this.Remove(key);
+ }
+ }
+
+ public bool Contains(KeyValuePair<string, string> item) {
+ MessagePart part;
+ if (this.description.Mapping.TryGetValue(item.Key, out part)) {
+ return string.Equals(part.GetValue(this.message), item.Value, StringComparison.Ordinal);
+ } else {
+ return this.message.ExtraData.Contains(item);
+ }
+ }
+
+ void ICollection<KeyValuePair<string, string>>.CopyTo(KeyValuePair<string, string>[] array, int arrayIndex) {
+ foreach (var pair in (IDictionary<string, string>)this) {
+ array[arrayIndex++] = pair;
+ }
+ }
+
+ public int Count {
+ get { return this.Keys.Count; }
+ }
+
+ bool ICollection<KeyValuePair<string, string>>.IsReadOnly {
+ get { return false; }
+ }
+
+ public bool Remove(KeyValuePair<string, string> item) {
+ // We use contains because that checks that the value is equal as well.
+ if (((ICollection<KeyValuePair<string, string>>)this).Contains(item)) {
+ ((IDictionary<string, string>)this).Remove(item.Key);
+ return true;
+ }
+ return false;
+ }
+
+ #endregion
+
+ #region IEnumerable<KeyValuePair<string,string>> Members
+
+ public IEnumerator<KeyValuePair<string, string>> GetEnumerator() {
+ foreach (string key in Keys) {
+ yield return new KeyValuePair<string, string>(key, this[key]);
+ }
+ }
+
+ #endregion
+
+ #region IEnumerable Members
+
+ IEnumerator System.Collections.IEnumerable.GetEnumerator() {
+ return ((IEnumerable<KeyValuePair<string, string>>)this).GetEnumerator();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/Reflection/MessagePart.cs b/src/DotNetOAuth/Messaging/Reflection/MessagePart.cs new file mode 100644 index 0000000..0cf7cd4 --- /dev/null +++ b/src/DotNetOAuth/Messaging/Reflection/MessagePart.cs @@ -0,0 +1,148 @@ +//-----------------------------------------------------------------------
+// <copyright file="MessagePart.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging.Reflection {
+ using System;
+ using System.Collections.Generic;
+ using System.Net.Security;
+ using System.Reflection;
+ using System.Xml;
+ using System.Globalization;
+
+ internal class MessagePart {
+ private static readonly Dictionary<Type, ValueMapping> converters = new Dictionary<Type, ValueMapping>();
+
+ private ValueMapping converter;
+
+ private PropertyInfo property;
+
+ private FieldInfo field;
+
+ private Type memberDeclaredType;
+
+ private object defaultMemberValue;
+
+ static MessagePart() {
+ Map<Uri>(uri => uri.AbsoluteUri, str => new Uri(str));
+ Map<DateTime>(dt => XmlConvert.ToString(dt, XmlDateTimeSerializationMode.Utc), str => XmlConvert.ToDateTime(str, XmlDateTimeSerializationMode.Utc));
+ }
+
+ internal MessagePart(MemberInfo member, MessagePartAttribute attribute) {
+ if (member == null) {
+ throw new ArgumentNullException("member");
+ }
+
+ this.field = member as FieldInfo;
+ this.property = member as PropertyInfo;
+ if (this.field == null && this.property == null) {
+ throw new ArgumentException(string.Format(
+ CultureInfo.CurrentCulture,
+ MessagingStrings.UnexpectedType,
+ typeof(FieldInfo).Name + ", " + typeof(PropertyInfo).Name,
+ member.GetType().Name), "member");
+ }
+
+ if (attribute == null) {
+ throw new ArgumentNullException("attribute");
+ }
+
+ this.Name = attribute.Name ?? member.Name;
+ this.RequiredProtection = attribute.RequiredProtection;
+ this.IsRequired = attribute.IsRequired;
+ this.memberDeclaredType = (this.field != null) ? this.field.FieldType : this.property.PropertyType;
+ this.defaultMemberValue = deriveDefaultValue(this.memberDeclaredType);
+
+ if (!converters.TryGetValue(this.memberDeclaredType, out this.converter)) {
+ this.converter = new ValueMapping(
+ obj => obj != null ? obj.ToString() : null,
+ str => str != null ? Convert.ChangeType(str, memberDeclaredType) : null);
+ }
+
+ // Validate a sane combination of settings
+ ValidateSettings();
+ }
+
+ internal string Name { get; set; }
+
+ internal ProtectionLevel RequiredProtection { get; set; }
+
+ internal bool IsRequired { get; set; }
+
+ internal object ToValue(string value) {
+ return this.converter.StringToValue(value);
+ }
+
+ internal string ToString(object value) {
+ return this.converter.ValueToString(value);
+ }
+
+ internal void SetValue(IProtocolMessage message, string value) {
+ if (this.property != null) {
+ this.property.SetValue(message, this.ToValue(value), null);
+ } else {
+ this.field.SetValue(message, this.ToValue(value));
+ }
+ }
+
+ internal string GetValue(IProtocolMessage message) {
+ return this.ToString(this.GetValueAsObject(message));
+ }
+
+ internal bool IsNondefaultValueSet(IProtocolMessage message) {
+ if (this.memberDeclaredType.IsValueType) {
+ return !GetValueAsObject(message).Equals(this.defaultMemberValue);
+ } else {
+ return this.defaultMemberValue != GetValueAsObject(message);
+ }
+ }
+
+ private static object deriveDefaultValue(Type type) {
+ if (type.IsValueType) {
+ return Activator.CreateInstance(type);
+ } else {
+ return null;
+ }
+ }
+
+ private object GetValueAsObject(IProtocolMessage message) {
+ if (this.property != null) {
+ return this.property.GetValue(message, null);
+ } else {
+ return this.field.GetValue(message);
+ }
+ }
+
+ private static void Map<T>(Func<T, string> toString, Func<string, T> toValue) {
+ converters.Add(
+ typeof(T),
+ new ValueMapping(
+ obj => obj != null ? toString((T)obj) : null,
+ str => str != null ? toValue(str) : default(T)));
+ }
+
+ private void ValidateSettings() {
+ // An optional tag on a non-nullable value type is a contradiction.
+ if (!this.IsRequired && IsNonNullableValueType(this.memberDeclaredType)) {
+ MemberInfo member = (MemberInfo)this.field ?? this.property;
+ throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
+ "Invalid combination: {0} on message type {1} is a non-nullable value type but is marked as optional.",
+ member.Name, member.DeclaringType));
+ }
+ }
+
+ private static bool IsNonNullableValueType(Type type) {
+ if (!type.IsValueType) {
+ return false;
+ }
+
+ if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) {
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/Reflection/MessagePartAttribute.cs b/src/DotNetOAuth/Messaging/Reflection/MessagePartAttribute.cs new file mode 100644 index 0000000..c8bb8f5 --- /dev/null +++ b/src/DotNetOAuth/Messaging/Reflection/MessagePartAttribute.cs @@ -0,0 +1,33 @@ +//-----------------------------------------------------------------------
+// <copyright file="MessagePartAttribute.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging.Reflection {
+ using System;
+ using System.Net.Security;
+ using System.Reflection;
+
+ [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
+ internal sealed class MessagePartAttribute : Attribute {
+ private bool initialized;
+ private string name;
+
+ internal MessagePartAttribute() {
+ }
+
+ internal MessagePartAttribute(string name) {
+ this.Name = name;
+ }
+
+ public string Name {
+ get { return this.name; }
+ set { this.name = string.IsNullOrEmpty(value) ? null : value; }
+ }
+
+ public ProtectionLevel RequiredProtection { get; set; }
+
+ public bool IsRequired { get; set; }
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/Reflection/ValueMapping.cs b/src/DotNetOAuth/Messaging/Reflection/ValueMapping.cs new file mode 100644 index 0000000..2371b49 --- /dev/null +++ b/src/DotNetOAuth/Messaging/Reflection/ValueMapping.cs @@ -0,0 +1,27 @@ +//-----------------------------------------------------------------------
+// <copyright file="ValueMapping.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging.Reflection {
+ using System;
+
+ internal struct ValueMapping {
+ internal Func<object, string> ValueToString;
+ internal Func<string, object> StringToValue;
+
+ internal ValueMapping(Func<object, string> toString, Func<string, object> toValue) {
+ if (toString == null) {
+ throw new ArgumentNullException("toString");
+ }
+
+ if (toValue == null) {
+ throw new ArgumentNullException("toValue");
+ }
+
+ this.ValueToString = toString;
+ this.StringToValue = toValue;
+ }
+ }
+}
|