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.
237 lines
9.7 KiB
237 lines
9.7 KiB
/* Copyright 2010 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;
|
|
|
|
namespace MongoDB.Bson.DefaultSerializer {
|
|
public class BsonClassMapSerializer : IBsonSerializer {
|
|
#region private static fields
|
|
private static BsonClassMapSerializer singleton = new BsonClassMapSerializer();
|
|
#endregion
|
|
|
|
#region constructors
|
|
private BsonClassMapSerializer() {
|
|
}
|
|
#endregion
|
|
|
|
#region public static properties
|
|
public static BsonClassMapSerializer Singleton {
|
|
get { return singleton; }
|
|
}
|
|
#endregion
|
|
|
|
#region public methods
|
|
public object Deserialize(
|
|
BsonReader bsonReader,
|
|
Type nominalType
|
|
) {
|
|
if (bsonReader.CurrentBsonType == Bson.BsonType.Null) {
|
|
bsonReader.ReadNull();
|
|
return null;
|
|
} else {
|
|
VerifyNominalType(nominalType);
|
|
|
|
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, actualType);
|
|
}
|
|
}
|
|
|
|
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!
|
|
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) {
|
|
DeserializeMember(bsonReader, obj, memberMap);
|
|
missingElementMemberMaps.Remove(memberMap);
|
|
} else {
|
|
// TODO: send extra elements to a catch-all property
|
|
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 BsonSerializationException(message);
|
|
}
|
|
|
|
if (memberMap.HasDefaultValue) {
|
|
memberMap.ApplyDefaultValue(obj);
|
|
}
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
}
|
|
|
|
public bool DocumentHasIdMember(
|
|
object document
|
|
) {
|
|
var classMap = BsonClassMap.LookupClassMap(document.GetType());
|
|
return classMap.IdMemberMap != null;
|
|
}
|
|
|
|
public bool DocumentHasIdValue(
|
|
object document,
|
|
out object existingId
|
|
) {
|
|
var classMap = BsonClassMap.LookupClassMap(document.GetType());
|
|
var idMemberMap = classMap.IdMemberMap;
|
|
existingId = idMemberMap.Getter(document);
|
|
var idGenerator = idMemberMap.IdGenerator;
|
|
return idGenerator != null && !idGenerator.IsEmpty(existingId);
|
|
}
|
|
|
|
public void GenerateDocumentId(
|
|
object document
|
|
) {
|
|
var classMap = BsonClassMap.LookupClassMap(document.GetType());
|
|
var idMemberMap = classMap.IdMemberMap;
|
|
var idGenerator = idMemberMap.IdGenerator;
|
|
if (idGenerator != null) {
|
|
idMemberMap.Setter(document, idGenerator.GenerateId());
|
|
}
|
|
}
|
|
|
|
public void Serialize(
|
|
BsonWriter bsonWriter,
|
|
Type nominalType,
|
|
object value,
|
|
bool serializeIdFirst
|
|
) {
|
|
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();
|
|
BsonMemberMap idMemberMap = null;
|
|
if (serializeIdFirst) {
|
|
idMemberMap = classMap.IdMemberMap;
|
|
if (idMemberMap != null) {
|
|
SerializeMember(bsonWriter, value, idMemberMap);
|
|
}
|
|
}
|
|
|
|
if (actualType != nominalType || classMap.DiscriminatorIsRequired || classMap.HasRootClass) {
|
|
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) {
|
|
SerializeMember(bsonWriter, value, memberMap);
|
|
}
|
|
}
|
|
bsonWriter.WriteEndDocument();
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region private methods
|
|
private void DeserializeMember(
|
|
BsonReader bsonReader,
|
|
object obj,
|
|
BsonMemberMap memberMap
|
|
) {
|
|
var nominalType = memberMap.MemberType;
|
|
var discriminatorConvention = BsonDefaultSerializer.LookupDiscriminatorConvention(nominalType);
|
|
var actualType = discriminatorConvention.GetActualType(bsonReader, nominalType); // returns nominalType if no discriminator found
|
|
var serializer = memberMap.GetSerializerForActualType(actualType);
|
|
object value = serializer.Deserialize(bsonReader, memberMap.MemberType);
|
|
memberMap.Setter(obj, value);
|
|
}
|
|
|
|
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 && value.Equals(memberMap.DefaultValue)) {
|
|
return; // don't serialize default value
|
|
}
|
|
|
|
var nominalType = memberMap.MemberType;
|
|
var actualType = (value == null) ? nominalType : value.GetType();
|
|
var serializer = memberMap.GetSerializerForActualType(actualType);
|
|
var elementName = memberMap.ElementName;
|
|
bsonWriter.WriteName(elementName);
|
|
serializer.Serialize(bsonWriter, nominalType, value, false);
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|