//-----------------------------------------------------------------------
//
// Copyright (c) Outercurve Foundation. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.Messaging {
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Reflection;
using System.Xml;
using DotNetOpenAuth.Messaging.Reflection;
using Validation;
///
/// Serializes/deserializes OAuth messages for/from transit.
///
internal class MessageSerializer {
///
/// The specific -derived type
/// that will be serialized and deserialized using this class.
///
private readonly Type messageType;
///
/// Initializes a new instance of the MessageSerializer class.
///
/// The specific -derived type
/// that will be serialized and deserialized using this class.
private MessageSerializer(Type messageType) {
RequiresEx.NotNullSubtype(messageType, "messageType");
this.messageType = messageType;
}
///
/// Creates or reuses a message serializer for a given message type.
///
/// The type of message that will be serialized/deserialized.
/// A message serializer for the given message type.
internal static MessageSerializer Get(Type messageType) {
RequiresEx.NotNullSubtype(messageType, "messageType");
return new MessageSerializer(messageType);
}
///
/// Reads JSON as a flat dictionary into a message.
///
/// The message dictionary to fill with the JSON-deserialized data.
/// The JSON reader.
internal static void DeserializeJsonAsFlatDictionary(IDictionary messageDictionary, XmlDictionaryReader reader) {
Requires.NotNull(messageDictionary, "messageDictionary");
Requires.NotNull(reader, "reader");
reader.Read(); // one extra one to skip the root node.
while (reader.Read()) {
if (reader.NodeType == XmlNodeType.EndElement) {
// This is likely the closing tag.
continue;
}
string key = reader.Name;
reader.Read();
string value = reader.ReadContentAsString();
messageDictionary[key] = value;
}
}
///
/// Reads the data from a message instance and writes an XML/JSON encoding of it.
///
/// The message to be serialized.
/// The writer to use for the serialized form.
///
/// Use
/// to create the instance capable of emitting JSON.
///
[Pure]
internal static void Serialize(MessageDictionary messageDictionary, XmlDictionaryWriter writer) {
Requires.NotNull(messageDictionary, "messageDictionary");
Requires.NotNull(writer, "writer");
writer.WriteStartElement("root");
writer.WriteAttributeString("type", "object");
foreach (var pair in messageDictionary) {
bool include = false;
string type = "string";
MessagePart partDescription;
if (messageDictionary.Description.Mapping.TryGetValue(pair.Key, out partDescription)) {
Assumes.True(partDescription != null);
if (partDescription.IsRequired || partDescription.IsNondefaultValueSet(messageDictionary.Message)) {
include = true;
Type formattingType = partDescription.PreferredFormattingType;
if (IsNumeric(formattingType)) {
type = "number";
} else if (formattingType.IsAssignableFrom(typeof(bool))) {
type = "boolean";
}
}
} else {
// This is extra data. We always write it out.
include = true;
}
if (include) {
writer.WriteStartElement(pair.Key);
writer.WriteAttributeString("type", type);
writer.WriteString(pair.Value);
writer.WriteEndElement();
}
}
writer.WriteEndElement();
}
///
/// Reads XML/JSON into a message dictionary.
///
/// The message to deserialize into.
/// The XML/JSON to read into the message.
/// Thrown when protocol rules are broken by the incoming message.
///
/// Use
/// to create the instance capable of reading JSON.
///
internal static void Deserialize(MessageDictionary messageDictionary, XmlDictionaryReader reader) {
Requires.NotNull(messageDictionary, "messageDictionary");
Requires.NotNull(reader, "reader");
DeserializeJsonAsFlatDictionary(messageDictionary, reader);
// Make sure all the required parts are present and valid.
messageDictionary.Description.EnsureMessagePartsPassBasicValidation(messageDictionary);
messageDictionary.Message.EnsureValidMessage();
}
///
/// Reads the data from a message instance and returns a series of name=value pairs for the fields that must be included in the message.
///
/// The message to be serialized.
/// The dictionary of values to send for the message.
[Pure]
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Parallel design with Deserialize method.")]
internal IDictionary Serialize(MessageDictionary messageDictionary) {
Requires.NotNull(messageDictionary, "messageDictionary");
// Rather than hand back the whole message dictionary (which
// includes keys with blank values), create a new dictionary
// that only has required keys, and optional keys whose
// values are not empty (or default).
var result = new Dictionary();
foreach (var pair in messageDictionary) {
MessagePart partDescription;
if (messageDictionary.Description.Mapping.TryGetValue(pair.Key, out partDescription)) {
Assumes.True(partDescription != null);
if (partDescription.IsRequired || partDescription.IsNondefaultValueSet(messageDictionary.Message)) {
result.Add(pair.Key, pair.Value);
}
} else {
// This is extra data. We always write it out.
result.Add(pair.Key, pair.Value);
}
}
return result;
}
///
/// Reads name=value pairs into a message.
///
/// The name=value pairs that were read in from the transport.
/// The message to deserialize into.
/// Thrown when protocol rules are broken by the incoming message.
internal void Deserialize(IDictionary fields, MessageDictionary messageDictionary) {
Requires.NotNull(fields, "fields");
Requires.NotNull(messageDictionary, "messageDictionary");
var messageDescription = messageDictionary.Description;
// Before we deserialize the message, make sure all the required parts are present.
messageDescription.EnsureMessagePartsPassBasicValidation(fields);
try {
foreach (var pair in fields) {
messageDictionary[pair.Key] = pair.Value;
}
} catch (ArgumentException ex) {
throw ErrorUtilities.Wrap(ex, MessagingStrings.ErrorDeserializingMessage, this.messageType.Name);
}
messageDictionary.Message.EnsureValidMessage();
var originalPayloadMessage = messageDictionary.Message as IMessageOriginalPayload;
if (originalPayloadMessage != null) {
originalPayloadMessage.OriginalPayload = fields;
}
}
///
/// Determines whether the specified type is numeric.
///
/// The type to test.
///
/// true if the specified type is numeric; otherwise, false.
///
private static bool IsNumeric(Type type) {
return type.IsAssignableFrom(typeof(double))
|| type.IsAssignableFrom(typeof(float))
|| type.IsAssignableFrom(typeof(short))
|| type.IsAssignableFrom(typeof(int))
|| type.IsAssignableFrom(typeof(long))
|| type.IsAssignableFrom(typeof(ushort))
|| type.IsAssignableFrom(typeof(uint))
|| type.IsAssignableFrom(typeof(ulong));
}
#if CONTRACTS_FULL
///
/// Verifies conditions that should be true for any valid state of this object.
///
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")]
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")]
[ContractInvariantMethod]
private void ObjectInvariant() {
Contract.Invariant(this.messageType != null);
}
#endif
}
}