//-----------------------------------------------------------------------
//
// Copyright (c) Outercurve Foundation. 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;
using Validation;
///
/// 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) {
RequiresEx.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");
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");
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} ({3}) with these message parts:{1}{2}",
this.MessageType.Name,
Environment.NewLine,
parts.ToStringDeferred(),
this.MessageVersion);
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
}
}