You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
348 lines
14 KiB
348 lines
14 KiB
/* Copyright 2010-2011 10gen Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
|
|
using MongoDB.Bson.IO;
|
|
using MongoDB.Bson.Serialization.Options;
|
|
|
|
namespace MongoDB.Bson.Serialization {
|
|
/// <summary>
|
|
/// Represents a serializer for a class map.
|
|
/// </summary>
|
|
public class BsonClassMapSerializer : IBsonSerializer {
|
|
#region private static fields
|
|
private static BsonClassMapSerializer instance = new BsonClassMapSerializer();
|
|
#endregion
|
|
|
|
#region constructors
|
|
/// <summary>
|
|
/// Initializes a new instance of the BsonClassMapSerializer class.
|
|
/// </summary>
|
|
public BsonClassMapSerializer() {
|
|
}
|
|
#endregion
|
|
|
|
#region public static properties
|
|
/// <summary>
|
|
/// Gets an instance of the BsonClassMapSerializer class.
|
|
/// </summary>
|
|
public static BsonClassMapSerializer Instance {
|
|
get { return instance; }
|
|
}
|
|
#endregion
|
|
|
|
#region public methods
|
|
/// <summary>
|
|
/// Deserializes an object from a BsonReader.
|
|
/// </summary>
|
|
/// <param name="bsonReader">The BsonReader.</param>
|
|
/// <param name="nominalType">The nominal type of the object.</param>
|
|
/// <param name="options">The serialization options.</param>
|
|
/// <returns>An object.</returns>
|
|
public object Deserialize(
|
|
BsonReader bsonReader,
|
|
Type nominalType,
|
|
IBsonSerializationOptions options
|
|
) {
|
|
VerifyNominalType(nominalType);
|
|
if (bsonReader.CurrentBsonType == Bson.BsonType.Null) {
|
|
bsonReader.ReadNull();
|
|
return null;
|
|
} else {
|
|
var discriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(nominalType);
|
|
var actualType = discriminatorConvention.GetActualType(bsonReader, nominalType);
|
|
if (actualType != nominalType) {
|
|
var serializer = BsonSerializer.LookupSerializer(actualType);
|
|
if (serializer != this) {
|
|
// in rare cases a concrete actualType might have a more specialized serializer
|
|
return serializer.Deserialize(bsonReader, nominalType, actualType, options);
|
|
}
|
|
}
|
|
|
|
return Deserialize(bsonReader, nominalType, actualType, options);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deserializes an object from a BsonReader.
|
|
/// </summary>
|
|
/// <param name="bsonReader">The BsonReader.</param>
|
|
/// <param name="nominalType">The nominal type of the object.</param>
|
|
/// <param name="actualType">The actual type of the object.</param>
|
|
/// <param name="options">The serialization options.</param>
|
|
/// <returns>An object.</returns>
|
|
public object Deserialize(
|
|
BsonReader bsonReader,
|
|
Type nominalType,
|
|
Type actualType,
|
|
IBsonSerializationOptions options
|
|
) {
|
|
VerifyNominalType(nominalType);
|
|
if (bsonReader.CurrentBsonType == Bson.BsonType.Null) {
|
|
bsonReader.ReadNull();
|
|
return null;
|
|
} else {
|
|
var classMap = BsonClassMap.LookupClassMap(actualType);
|
|
if (classMap.IsAnonymous) {
|
|
throw new InvalidOperationException("Anonymous classes cannot be deserialized");
|
|
}
|
|
var obj = classMap.CreateInstance();
|
|
|
|
bsonReader.ReadStartDocument();
|
|
var missingElementMemberMaps = new HashSet<BsonMemberMap>(classMap.MemberMaps); // make a copy!
|
|
var discriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(nominalType);
|
|
while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) {
|
|
var elementName = bsonReader.ReadName();
|
|
if (elementName == discriminatorConvention.ElementName) {
|
|
bsonReader.SkipValue(); // skip over discriminator
|
|
continue;
|
|
}
|
|
|
|
var memberMap = classMap.GetMemberMapForElement(elementName);
|
|
if (memberMap != null && memberMap != classMap.ExtraElementsMemberMap) {
|
|
DeserializeMember(bsonReader, obj, memberMap);
|
|
missingElementMemberMaps.Remove(memberMap);
|
|
} else {
|
|
if (classMap.ExtraElementsMemberMap != null) {
|
|
DeserializeExtraElement(bsonReader, obj, elementName, classMap.ExtraElementsMemberMap);
|
|
} else if (classMap.IgnoreExtraElements) {
|
|
bsonReader.SkipValue();
|
|
} else {
|
|
string message = string.Format("Unexpected element: {0}", elementName);
|
|
throw new FileFormatException(message);
|
|
}
|
|
}
|
|
}
|
|
bsonReader.ReadEndDocument();
|
|
|
|
foreach (var memberMap in missingElementMemberMaps) {
|
|
if (memberMap.IsRequired) {
|
|
var message = string.Format("Required element is missing: {0}", memberMap.ElementName);
|
|
throw new FileFormatException(message);
|
|
}
|
|
|
|
if (memberMap.HasDefaultValue) {
|
|
memberMap.ApplyDefaultValue(obj);
|
|
}
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the document Id.
|
|
/// </summary>
|
|
/// <param name="document">The document.</param>
|
|
/// <param name="id">The Id.</param>
|
|
/// <param name="idNominalType">The nominal type of the Id.</param>
|
|
/// <param name="idGenerator">The IdGenerator for the Id type.</param>
|
|
/// <returns>True if the document has an Id.</returns>
|
|
public bool GetDocumentId(
|
|
object document,
|
|
out object id,
|
|
out Type idNominalType,
|
|
out IIdGenerator idGenerator
|
|
) {
|
|
var classMap = BsonClassMap.LookupClassMap(document.GetType());
|
|
var idMemberMap = classMap.IdMemberMap;
|
|
if (idMemberMap != null) {
|
|
id = idMemberMap.Getter(document);
|
|
idNominalType = idMemberMap.MemberType;
|
|
idGenerator = idMemberMap.IdGenerator;
|
|
return true;
|
|
} else {
|
|
id = null;
|
|
idNominalType = null;
|
|
idGenerator = null;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializes an object to a BsonWriter.
|
|
/// </summary>
|
|
/// <param name="bsonWriter">The BsonWriter.</param>
|
|
/// <param name="nominalType">The nominal type.</param>
|
|
/// <param name="value">The object.</param>
|
|
/// <param name="options">The serialization options.</param>
|
|
public void Serialize(
|
|
BsonWriter bsonWriter,
|
|
Type nominalType,
|
|
object value,
|
|
IBsonSerializationOptions options
|
|
) {
|
|
if (value == null) {
|
|
bsonWriter.WriteNull();
|
|
} else {
|
|
// Nullable types are weird because they get boxed as their underlying value type
|
|
// we can best handle that by switching the nominalType to the underlying value type
|
|
// (so VerifyNominalType doesn't fail and we don't get an unnecessary discriminator)
|
|
if (nominalType.IsGenericType && nominalType.GetGenericTypeDefinition() == typeof(Nullable<>)) {
|
|
nominalType = nominalType.GetGenericArguments()[0];
|
|
}
|
|
|
|
VerifyNominalType(nominalType);
|
|
var actualType = (value == null) ? nominalType : value.GetType();
|
|
var classMap = BsonClassMap.LookupClassMap(actualType);
|
|
|
|
bsonWriter.WriteStartDocument();
|
|
var documentOptions = (options == null) ? DocumentSerializationOptions.Defaults : (DocumentSerializationOptions) options;
|
|
BsonMemberMap idMemberMap = null;
|
|
if (documentOptions.SerializeIdFirst) {
|
|
idMemberMap = classMap.IdMemberMap;
|
|
if (idMemberMap != null) {
|
|
SerializeMember(bsonWriter, value, idMemberMap);
|
|
}
|
|
}
|
|
|
|
if (actualType != nominalType || classMap.DiscriminatorIsRequired || classMap.HasRootClass) {
|
|
// never write out a discriminator for an anonymous class
|
|
if (!classMap.IsAnonymous) {
|
|
var discriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(nominalType);
|
|
var discriminator = discriminatorConvention.GetDiscriminator(nominalType, actualType);
|
|
if (discriminator != null) {
|
|
bsonWriter.WriteName(discriminatorConvention.ElementName);
|
|
discriminator.WriteTo(bsonWriter);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var memberMap in classMap.MemberMaps) {
|
|
// note: if serializeIdFirst is false then idMemberMap will be null (so no property will be skipped)
|
|
if (memberMap != idMemberMap) {
|
|
if (memberMap == classMap.ExtraElementsMemberMap) {
|
|
SerializeExtraElements(bsonWriter, value, memberMap);
|
|
} else {
|
|
SerializeMember(bsonWriter, value, memberMap);
|
|
}
|
|
}
|
|
}
|
|
bsonWriter.WriteEndDocument();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the document Id.
|
|
/// </summary>
|
|
/// <param name="document">The document.</param>
|
|
/// <param name="id">The Id.</param>
|
|
public void SetDocumentId(
|
|
object document,
|
|
object id
|
|
) {
|
|
var classMap = BsonClassMap.LookupClassMap(document.GetType());
|
|
var idMemberMap = classMap.IdMemberMap;
|
|
if (idMemberMap != null) {
|
|
idMemberMap.Setter(document, id);
|
|
} else {
|
|
var message = string.Format("Class {0} has no Id member", document.GetType());
|
|
throw new InvalidOperationException(message);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region private methods
|
|
private void DeserializeExtraElement(
|
|
BsonReader bsonReader,
|
|
object obj,
|
|
string elementName,
|
|
BsonMemberMap extraElementsMemberMap
|
|
) {
|
|
var extraElements = (BsonDocument) extraElementsMemberMap.Getter(obj);
|
|
if (extraElements == null) {
|
|
extraElements = new BsonDocument();
|
|
extraElementsMemberMap.Setter(obj, extraElements);
|
|
}
|
|
var value = BsonValue.ReadFrom(bsonReader);
|
|
extraElements[elementName] = value;
|
|
}
|
|
|
|
private void DeserializeMember(
|
|
BsonReader bsonReader,
|
|
object obj,
|
|
BsonMemberMap memberMap
|
|
) {
|
|
var nominalType = memberMap.MemberType;
|
|
Type actualType;
|
|
if (bsonReader.CurrentBsonType == BsonType.Null) {
|
|
actualType = nominalType;
|
|
} else {
|
|
var discriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(nominalType);
|
|
actualType = discriminatorConvention.GetActualType(bsonReader, nominalType); // returns nominalType if no discriminator found
|
|
}
|
|
var serializer = memberMap.GetSerializer(actualType);
|
|
var value = serializer.Deserialize(bsonReader, nominalType, actualType, memberMap.SerializationOptions);
|
|
memberMap.Setter(obj, value);
|
|
}
|
|
|
|
private void SerializeExtraElements(
|
|
BsonWriter bsonWriter,
|
|
object obj,
|
|
BsonMemberMap extraElementsMemberMap
|
|
) {
|
|
var extraElements = (BsonDocument) extraElementsMemberMap.Getter(obj);
|
|
if (extraElements != null) {
|
|
foreach (var element in extraElements) {
|
|
element.WriteTo(bsonWriter);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SerializeMember(
|
|
BsonWriter bsonWriter,
|
|
object obj,
|
|
BsonMemberMap memberMap
|
|
) {
|
|
var value = memberMap.Getter(obj);
|
|
if (value == null && memberMap.IgnoreIfNull) {
|
|
return; // don't serialize null value
|
|
}
|
|
if (memberMap.HasDefaultValue && !memberMap.SerializeDefaultValue && object.Equals(value, memberMap.DefaultValue)) {
|
|
return; // don't serialize default value
|
|
}
|
|
if (!memberMap.ShouldSerializeValue(obj))
|
|
{
|
|
return;// the object determined that it does not need serialization.
|
|
}
|
|
|
|
bsonWriter.WriteName(memberMap.ElementName);
|
|
var nominalType = memberMap.MemberType;
|
|
var actualType = (value == null) ? nominalType : value.GetType();
|
|
var serializer = memberMap.GetSerializer(actualType);
|
|
serializer.Serialize(bsonWriter, nominalType, value, memberMap.SerializationOptions);
|
|
}
|
|
|
|
private void VerifyNominalType(
|
|
Type nominalType
|
|
) {
|
|
if (
|
|
!(nominalType.IsClass || (nominalType.IsValueType && !nominalType.IsPrimitive) || nominalType.IsInterface) ||
|
|
typeof(Array).IsAssignableFrom(nominalType)
|
|
) {
|
|
string message = string.Format("BsonClassMapSerializer cannot be used with type: {0}", nominalType.FullName);
|
|
throw new BsonSerializationException(message);
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|