//----------------------------------------------------------------------- // // Copyright (c) Andrew Arnott. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.Messaging.Reflection { using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Contracts; using System.Globalization; using System.Linq; using System.Reflection; /// /// A mapping between serialized key names and instances describing /// those key/values pairs. /// internal class MessageDescription { /// /// A mapping between the serialized key names and their /// describing instances. /// private Dictionary mapping; /// /// Initializes a new instance of the class. /// /// Type of the message. /// The message version. internal MessageDescription(Type messageType, Version messageVersion) { Requires.NotNullSubtype(messageType, "messageType"); Requires.NotNull(messageVersion, "messageVersion"); this.MessageType = messageType; this.MessageVersion = messageVersion; this.ReflectMessageType(); } /// /// Gets the mapping between the serialized key names and their describing /// instances. /// internal IDictionary Mapping { get { return this.mapping; } } /// /// Gets the message version this instance was generated from. /// internal Version MessageVersion { get; private set; } /// /// Gets the type of message this instance was generated from. /// /// The type of the described message. internal Type MessageType { get; private set; } /// /// Gets the constructors available on the message type. /// internal ConstructorInfo[] Constructors { get; private set; } /// /// Returns a that represents this instance. /// /// /// A that represents this instance. /// public override string ToString() { return this.MessageType.Name + " (" + this.MessageVersion + ")"; } /// /// Gets a dictionary that provides read/write access to a message. /// /// The message the dictionary should provide access to. /// The dictionary accessor to the message [Pure] internal MessageDictionary GetDictionary(IMessage message) { Requires.NotNull(message, "message"); Contract.Ensures(Contract.Result() != null); return this.GetDictionary(message, false); } /// /// Gets a dictionary that provides read/write access to a message. /// /// The message the dictionary should provide access to. /// A value indicating whether this message dictionary will retrieve original values instead of normalized ones. /// The dictionary accessor to the message [Pure] internal MessageDictionary GetDictionary(IMessage message, bool getOriginalValues) { Requires.NotNull(message, "message"); Contract.Ensures(Contract.Result() != null); return new MessageDictionary(message, this, getOriginalValues); } /// /// Ensures the message parts pass basic validation. /// /// The key/value pairs of the serialized message. internal void EnsureMessagePartsPassBasicValidation(IDictionary parts) { try { this.CheckRequiredMessagePartsArePresent(parts.Keys, true); this.CheckRequiredProtocolMessagePartsAreNotEmpty(parts, true); this.CheckMessagePartsConstantValues(parts, true); } catch (ProtocolException) { Logger.Messaging.ErrorFormat( "Error while performing basic validation of {0} with these message parts:{1}{2}", this.MessageType.Name, Environment.NewLine, parts.ToStringDeferred()); throw; } } /// /// Tests whether all the required message parts pass basic validation for the given data. /// /// The key/value pairs of the serialized message. /// A value indicating whether the provided data fits the message's basic requirements. internal bool CheckMessagePartsPassBasicValidation(IDictionary parts) { Requires.NotNull(parts, "parts"); return this.CheckRequiredMessagePartsArePresent(parts.Keys, false) && this.CheckRequiredProtocolMessagePartsAreNotEmpty(parts, false) && this.CheckMessagePartsConstantValues(parts, false); } /// /// Verifies that a given set of keys include all the required parameters /// for this message type or throws an exception. /// /// The names of all parameters included in a message. /// if set to true an exception is thrown on failure with details. /// A value indicating whether the provided data fits the message's basic requirements. /// /// Thrown when required parts of a message are not in /// if is true. /// private bool CheckRequiredMessagePartsArePresent(IEnumerable keys, bool throwOnFailure) { Requires.NotNull(keys, "keys"); var missingKeys = (from part in this.Mapping.Values where part.IsRequired && !keys.Contains(part.Name) select part.Name).ToArray(); if (missingKeys.Length > 0) { if (throwOnFailure) { ErrorUtilities.ThrowProtocol( MessagingStrings.RequiredParametersMissing, this.MessageType.FullName, string.Join(", ", missingKeys)); } else { Logger.Messaging.DebugFormat( MessagingStrings.RequiredParametersMissing, this.MessageType.FullName, missingKeys.ToStringDeferred()); return false; } } return true; } /// /// Ensures the protocol message parts that must not be empty are in fact not empty. /// /// A dictionary of key/value pairs that make up the serialized message. /// if set to true an exception is thrown on failure with details. /// A value indicating whether the provided data fits the message's basic requirements. /// /// Thrown when required parts of a message are not in /// if is true. /// private bool CheckRequiredProtocolMessagePartsAreNotEmpty(IDictionary partValues, bool throwOnFailure) { Requires.NotNull(partValues, "partValues"); string value; var emptyValuedKeys = (from part in this.Mapping.Values where !part.AllowEmpty && partValues.TryGetValue(part.Name, out value) && value != null && value.Length == 0 select part.Name).ToArray(); if (emptyValuedKeys.Length > 0) { if (throwOnFailure) { ErrorUtilities.ThrowProtocol( MessagingStrings.RequiredNonEmptyParameterWasEmpty, this.MessageType.FullName, string.Join(", ", emptyValuedKeys)); } else { Logger.Messaging.DebugFormat( MessagingStrings.RequiredNonEmptyParameterWasEmpty, this.MessageType.FullName, emptyValuedKeys.ToStringDeferred()); return false; } } return true; } /// /// Checks that a bunch of message part values meet the constant value requirements of this message description. /// /// The part values. /// if set to true, this method will throw on failure. /// A value indicating whether all the requirements are met. private bool CheckMessagePartsConstantValues(IDictionary partValues, bool throwOnFailure) { Requires.NotNull(partValues, "partValues"); var badConstantValues = (from part in this.Mapping.Values where part.IsConstantValueAvailableStatically where partValues.ContainsKey(part.Name) where !string.Equals(partValues[part.Name], part.StaticConstantValue, StringComparison.Ordinal) select part.Name).ToArray(); if (badConstantValues.Length > 0) { if (throwOnFailure) { ErrorUtilities.ThrowProtocol( MessagingStrings.RequiredMessagePartConstantIncorrect, this.MessageType.FullName, string.Join(", ", badConstantValues)); } else { Logger.Messaging.DebugFormat( MessagingStrings.RequiredMessagePartConstantIncorrect, this.MessageType.FullName, badConstantValues.ToStringDeferred()); return false; } } return true; } /// /// Reflects over some -implementing type /// and prepares to serialize/deserialize instances of that type. /// private void ReflectMessageType() { this.mapping = new Dictionary(); Type currentType = this.MessageType; do { foreach (MemberInfo member in currentType.GetMembers(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)) { if (member is PropertyInfo || member is FieldInfo) { MessagePartAttribute partAttribute = (from a in member.GetCustomAttributes(typeof(MessagePartAttribute), true).OfType() orderby a.MinVersionValue descending where a.MinVersionValue <= this.MessageVersion where a.MaxVersionValue >= this.MessageVersion select a).FirstOrDefault(); if (partAttribute != null) { MessagePart part = new MessagePart(member, partAttribute); if (this.mapping.ContainsKey(part.Name)) { Logger.Messaging.WarnFormat( "Message type {0} has more than one message part named {1}. Inherited members will be hidden.", this.MessageType.Name, part.Name); } else { this.mapping.Add(part.Name, part); } } } } currentType = currentType.BaseType; } while (currentType != null); BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; this.Constructors = this.MessageType.GetConstructors(flags); } #if CONTRACTS_FULL /// /// Describes traits of this class that are always true. /// [ContractInvariantMethod] private void Invariant() { Contract.Invariant(this.MessageType != null); Contract.Invariant(this.MessageVersion != null); Contract.Invariant(this.Constructors != null); } #endif } }