PageRenderTime 62ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/source/dotnetopenid/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs

http://myspaceid-csharp-sdk.googlecode.com/
C# | 249 lines | 123 code | 27 blank | 99 comment | 22 complexity | 717668a0ee17167326898caf8bb14674 MD5 | raw file
  1. //-----------------------------------------------------------------------
  2. // <copyright file="MessageDescription.cs" company="Andrew Arnott">
  3. // Copyright (c) Andrew Arnott. All rights reserved.
  4. // </copyright>
  5. //-----------------------------------------------------------------------
  6. namespace DotNetOpenAuth.Messaging.Reflection {
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Diagnostics;
  10. using System.Globalization;
  11. using System.Linq;
  12. using System.Reflection;
  13. /// <summary>
  14. /// A mapping between serialized key names and <see cref="MessagePart"/> instances describing
  15. /// those key/values pairs.
  16. /// </summary>
  17. internal class MessageDescription {
  18. /// <summary>
  19. /// A dictionary of reflected message types and the generated reflection information.
  20. /// </summary>
  21. private static Dictionary<MessageTypeAndVersion, MessageDescription> reflectedMessageTypes = new Dictionary<MessageTypeAndVersion, MessageDescription>();
  22. /// <summary>
  23. /// The type of message this instance was generated from.
  24. /// </summary>
  25. private MessageTypeAndVersion messageTypeAndVersion;
  26. /// <summary>
  27. /// A mapping between the serialized key names and their
  28. /// describing <see cref="MessagePart"/> instances.
  29. /// </summary>
  30. private Dictionary<string, MessagePart> mapping;
  31. /// <summary>
  32. /// Initializes a new instance of the <see cref="MessageDescription"/> class.
  33. /// </summary>
  34. /// <param name="messageTypeAndVersion">The type and protocol version of the message to reflect over.</param>
  35. private MessageDescription(MessageTypeAndVersion messageTypeAndVersion) {
  36. ErrorUtilities.VerifyArgumentNotNull(messageTypeAndVersion, "messageTypeAndVersion");
  37. if (!typeof(IMessage).IsAssignableFrom(messageTypeAndVersion.Type)) {
  38. throw new ArgumentException(string.Format(
  39. CultureInfo.CurrentCulture,
  40. MessagingStrings.UnexpectedType,
  41. typeof(IMessage),
  42. messageTypeAndVersion.Type));
  43. }
  44. this.messageTypeAndVersion = messageTypeAndVersion;
  45. this.ReflectMessageType();
  46. }
  47. /// <summary>
  48. /// Gets the mapping between the serialized key names and their describing
  49. /// <see cref="MessagePart"/> instances.
  50. /// </summary>
  51. internal IDictionary<string, MessagePart> Mapping {
  52. get { return this.mapping; }
  53. }
  54. /// <summary>
  55. /// Gets a <see cref="MessageDescription"/> instance prepared for the
  56. /// given message type.
  57. /// </summary>
  58. /// <param name="messageType">A type that implements <see cref="IMessage"/>.</param>
  59. /// <param name="messageVersion">The protocol version of the message.</param>
  60. /// <returns>A <see cref="MessageDescription"/> instance.</returns>
  61. internal static MessageDescription Get(Type messageType, Version messageVersion) {
  62. ErrorUtilities.VerifyArgumentNotNull(messageType, "messageType");
  63. ErrorUtilities.VerifyArgumentNotNull(messageVersion, "messageVersion");
  64. MessageTypeAndVersion key = new MessageTypeAndVersion(messageType, messageVersion);
  65. MessageDescription result;
  66. if (!reflectedMessageTypes.TryGetValue(key, out result)) {
  67. lock (reflectedMessageTypes) {
  68. if (!reflectedMessageTypes.TryGetValue(key, out result)) {
  69. reflectedMessageTypes[key] = result = new MessageDescription(key);
  70. }
  71. }
  72. }
  73. return result;
  74. }
  75. /// <summary>
  76. /// Reflects over some <see cref="IMessage"/>-implementing type
  77. /// and prepares to serialize/deserialize instances of that type.
  78. /// </summary>
  79. internal void ReflectMessageType() {
  80. this.mapping = new Dictionary<string, MessagePart>();
  81. Type currentType = this.messageTypeAndVersion.Type;
  82. do {
  83. foreach (MemberInfo member in currentType.GetMembers(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)) {
  84. if (member is PropertyInfo || member is FieldInfo) {
  85. MessagePartAttribute partAttribute =
  86. (from a in member.GetCustomAttributes(typeof(MessagePartAttribute), true).OfType<MessagePartAttribute>()
  87. orderby a.MinVersionValue descending
  88. where a.MinVersionValue <= this.messageTypeAndVersion.Version
  89. where a.MaxVersionValue >= this.messageTypeAndVersion.Version
  90. select a).FirstOrDefault();
  91. if (partAttribute != null) {
  92. MessagePart part = new MessagePart(member, partAttribute);
  93. this.mapping.Add(part.Name, part);
  94. }
  95. }
  96. }
  97. currentType = currentType.BaseType;
  98. } while (currentType != null);
  99. }
  100. /// <summary>
  101. /// Ensures the message parts pass basic validation.
  102. /// </summary>
  103. /// <param name="parts">The key/value pairs of the serialzied message.</param>
  104. internal void EnsureMessagePartsPassBasicValidation(IDictionary<string, string> parts) {
  105. this.EnsureRequiredMessagePartsArePresent(parts.Keys);
  106. this.EnsureRequiredProtocolMessagePartsAreNotEmpty(parts);
  107. }
  108. /// <summary>
  109. /// Verifies that a given set of keys include all the required parameters
  110. /// for this message type or throws an exception.
  111. /// </summary>
  112. /// <param name="keys">The names of all parameters included in a message.</param>
  113. /// <exception cref="ProtocolException">Thrown when required parts of a message are not in <paramref name="keys"/></exception>
  114. private void EnsureRequiredMessagePartsArePresent(IEnumerable<string> keys) {
  115. var missingKeys = (from part in Mapping.Values
  116. where part.IsRequired && !keys.Contains(part.Name)
  117. select part.Name).ToArray();
  118. if (missingKeys.Length > 0) {
  119. throw new ProtocolException(
  120. string.Format(
  121. CultureInfo.CurrentCulture,
  122. MessagingStrings.RequiredParametersMissing,
  123. this.messageTypeAndVersion.Type.FullName,
  124. string.Join(", ", missingKeys)));
  125. }
  126. }
  127. /// <summary>
  128. /// Ensures the protocol message parts that must not be empty are in fact not empty.
  129. /// </summary>
  130. /// <param name="partValues">A dictionary of key/value pairs that make up the serialized message.</param>
  131. private void EnsureRequiredProtocolMessagePartsAreNotEmpty(IDictionary<string, string> partValues) {
  132. string value;
  133. var emptyValuedKeys = (from part in Mapping.Values
  134. where !part.AllowEmpty && partValues.TryGetValue(part.Name, out value) && value != null && value.Length == 0
  135. select part.Name).ToArray();
  136. if (emptyValuedKeys.Length > 0) {
  137. throw new ProtocolException(
  138. string.Format(
  139. CultureInfo.CurrentCulture,
  140. MessagingStrings.RequiredNonEmptyParameterWasEmpty,
  141. this.messageTypeAndVersion.Type.FullName,
  142. string.Join(", ", emptyValuedKeys)));
  143. }
  144. }
  145. /// <summary>
  146. /// A struct used as the key to bundle message type and version.
  147. /// </summary>
  148. private struct MessageTypeAndVersion {
  149. /// <summary>
  150. /// Backing store for the <see cref="Type"/> property.
  151. /// </summary>
  152. private readonly Type type;
  153. /// <summary>
  154. /// Backing store for the <see cref="Version"/> property.
  155. /// </summary>
  156. private readonly Version version;
  157. /// <summary>
  158. /// Initializes a new instance of the <see cref="MessageTypeAndVersion"/> struct.
  159. /// </summary>
  160. /// <param name="messageType">Type of the message.</param>
  161. /// <param name="messageVersion">The message version.</param>
  162. internal MessageTypeAndVersion(Type messageType, Version messageVersion) {
  163. ErrorUtilities.VerifyArgumentNotNull(messageType, "messageType");
  164. ErrorUtilities.VerifyArgumentNotNull(messageVersion, "messageVersion");
  165. this.type = messageType;
  166. this.version = messageVersion;
  167. }
  168. /// <summary>
  169. /// Gets the message type.
  170. /// </summary>
  171. internal Type Type { get { return this.type; } }
  172. /// <summary>
  173. /// Gets the message version.
  174. /// </summary>
  175. internal Version Version { get { return this.version; } }
  176. /// <summary>
  177. /// Implements the operator ==.
  178. /// </summary>
  179. /// <param name="first">The first object to compare.</param>
  180. /// <param name="second">The second object to compare.</param>
  181. /// <returns>The result of the operator.</returns>
  182. public static bool operator ==(MessageTypeAndVersion first, MessageTypeAndVersion second) {
  183. // structs cannot be null, so this is safe
  184. return first.Equals(second);
  185. }
  186. /// <summary>
  187. /// Implements the operator !=.
  188. /// </summary>
  189. /// <param name="first">The first object to compare.</param>
  190. /// <param name="second">The second object to compare.</param>
  191. /// <returns>The result of the operator.</returns>
  192. public static bool operator !=(MessageTypeAndVersion first, MessageTypeAndVersion second) {
  193. // structs cannot be null, so this is safe
  194. return !first.Equals(second);
  195. }
  196. /// <summary>
  197. /// Indicates whether this instance and a specified object are equal.
  198. /// </summary>
  199. /// <param name="obj">Another object to compare to.</param>
  200. /// <returns>
  201. /// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
  202. /// </returns>
  203. public override bool Equals(object obj) {
  204. if (obj is MessageTypeAndVersion) {
  205. MessageTypeAndVersion other = (MessageTypeAndVersion)obj;
  206. return this.type == other.type && this.version == other.version;
  207. } else {
  208. return false;
  209. }
  210. }
  211. /// <summary>
  212. /// Returns the hash code for this instance.
  213. /// </summary>
  214. /// <returns>
  215. /// A 32-bit signed integer that is the hash code for this instance.
  216. /// </returns>
  217. public override int GetHashCode() {
  218. return this.type.GetHashCode();
  219. }
  220. }
  221. }
  222. }