/* Copyright 2010-present MongoDB 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.Threading;
// don't add using statement for MongoDB.Bson.Serialization.Serializers to minimize dependencies on DefaultSerializer
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization.Conventions;
using MongoDB.Bson.Serialization.IdGenerators;
using MongoDB.Bson.Serialization.Serializers;
namespace MongoDB.Bson.Serialization
{
///
/// A static class that represents the BSON serialization functionality.
///
public static class BsonSerializer
{
// private static fields
private static ReaderWriterLockSlim __configLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
private static Dictionary __idGenerators = new Dictionary();
private static Dictionary __discriminatorConventions = new Dictionary();
private static Dictionary> __discriminators = new Dictionary>();
private static HashSet __discriminatedTypes = new HashSet();
private static BsonSerializerRegistry __serializerRegistry;
private static TypeMappingSerializationProvider __typeMappingSerializationProvider;
private static HashSet __typesWithRegisteredKnownTypes = new HashSet();
private static bool __useNullIdChecker = false;
private static bool __useZeroIdChecker = false;
// static constructor
static BsonSerializer()
{
CreateSerializerRegistry();
RegisterIdGenerators();
}
// public static properties
///
/// Gets the serializer registry.
///
public static IBsonSerializerRegistry SerializerRegistry
{
get { return __serializerRegistry; }
}
///
/// Gets or sets whether to use the NullIdChecker on reference Id types that don't have an IdGenerator registered.
///
public static bool UseNullIdChecker
{
get { return __useNullIdChecker; }
set { __useNullIdChecker = value; }
}
///
/// Gets or sets whether to use the ZeroIdChecker on value Id types that don't have an IdGenerator registered.
///
public static bool UseZeroIdChecker
{
get { return __useZeroIdChecker; }
set { __useZeroIdChecker = value; }
}
// internal static properties
internal static ReaderWriterLockSlim ConfigLock
{
get { return __configLock; }
}
// public static methods
///
/// Deserializes an object from a BsonDocument.
///
/// The nominal type of the object.
/// The BsonDocument.
/// The configurator.
/// A deserialized value.
public static TNominalType Deserialize(BsonDocument document, Action configurator = null)
{
using (var bsonReader = new BsonDocumentReader(document))
{
return Deserialize(bsonReader, configurator);
}
}
///
/// Deserializes a value.
///
/// The nominal type of the object.
/// The BsonReader.
/// The configurator.
/// A deserialized value.
public static TNominalType Deserialize(IBsonReader bsonReader, Action configurator = null)
{
var serializer = LookupSerializer();
var context = BsonDeserializationContext.CreateRoot(bsonReader, configurator);
return serializer.Deserialize(context);
}
///
/// Deserializes an object from a BSON byte array.
///
/// The nominal type of the object.
/// The BSON byte array.
/// The configurator.
/// A deserialized value.
public static TNominalType Deserialize(byte[] bytes, Action configurator = null)
{
using (var buffer = new ByteArrayBuffer(bytes, isReadOnly: true))
using (var stream = new ByteBufferStream(buffer))
{
return Deserialize(stream, configurator);
}
}
///
/// Deserializes an object from a BSON Stream.
///
/// The nominal type of the object.
/// The BSON Stream.
/// The configurator.
/// A deserialized value.
public static TNominalType Deserialize(Stream stream, Action configurator = null)
{
using (var bsonReader = new BsonBinaryReader(stream))
{
return Deserialize(bsonReader, configurator);
}
}
///
/// Deserializes an object from a JSON string.
///
/// The nominal type of the object.
/// The JSON string.
/// The configurator.
/// A deserialized value.
public static TNominalType Deserialize(string json, Action configurator = null)
{
using (var bsonReader = new JsonReader(json))
{
return Deserialize(bsonReader, configurator);
}
}
///
/// Deserializes an object from a JSON TextReader.
///
/// The nominal type of the object.
/// The JSON TextReader.
/// The configurator.
/// A deserialized value.
public static TNominalType Deserialize(TextReader textReader, Action configurator = null)
{
using (var bsonReader = new JsonReader(textReader))
{
return Deserialize(bsonReader, configurator);
}
}
///
/// Deserializes an object from a BsonDocument.
///
/// The BsonDocument.
/// The nominal type of the object.
/// The configurator.
/// A deserialized value.
public static object Deserialize(BsonDocument document, Type nominalType, Action configurator = null)
{
using (var bsonReader = new BsonDocumentReader(document))
{
return Deserialize(bsonReader, nominalType, configurator);
}
}
///
/// Deserializes a value.
///
/// The BsonReader.
/// The nominal type of the object.
/// The configurator.
/// A deserialized value.
public static object Deserialize(IBsonReader bsonReader, Type nominalType, Action configurator = null)
{
var serializer = LookupSerializer(nominalType);
var context = BsonDeserializationContext.CreateRoot(bsonReader, configurator);
return serializer.Deserialize(context);
}
///
/// Deserializes an object from a BSON byte array.
///
/// The BSON byte array.
/// The nominal type of the object.
/// The configurator.
/// A deserialized value.
public static object Deserialize(byte[] bytes, Type nominalType, Action configurator = null)
{
using (var buffer = new ByteArrayBuffer(bytes, isReadOnly: true))
using (var stream = new ByteBufferStream(buffer))
{
return Deserialize(stream, nominalType, configurator);
}
}
///
/// Deserializes an object from a BSON Stream.
///
/// The BSON Stream.
/// The nominal type of the object.
/// The configurator.
/// A deserialized value.
public static object Deserialize(Stream stream, Type nominalType, Action configurator = null)
{
using (var bsonReader = new BsonBinaryReader(stream))
{
return Deserialize(bsonReader, nominalType, configurator);
}
}
///
/// Deserializes an object from a JSON string.
///
/// The JSON string.
/// The nominal type of the object.
/// The configurator.
/// A deserialized value.
public static object Deserialize(string json, Type nominalType, Action configurator = null)
{
using (var bsonReader = new JsonReader(json))
{
return Deserialize(bsonReader, nominalType, configurator);
}
}
///
/// Deserializes an object from a JSON TextReader.
///
/// The JSON TextReader.
/// The nominal type of the object.
/// The configurator.
/// A deserialized value.
public static object Deserialize(TextReader textReader, Type nominalType, Action configurator = null)
{
using (var bsonReader = new JsonReader(textReader))
{
return Deserialize(bsonReader, nominalType, configurator);
}
}
///
/// Returns whether the given type has any discriminators registered for any of its subclasses.
///
/// A Type.
/// True if the type is discriminated.
public static bool IsTypeDiscriminated(Type type)
{
var typeInfo = type.GetTypeInfo();
return typeInfo.IsInterface || __discriminatedTypes.Contains(type);
}
///
/// Looks up the actual type of an object to be deserialized.
///
/// The nominal type of the object.
/// The discriminator.
/// The actual type of the object.
public static Type LookupActualType(Type nominalType, BsonValue discriminator)
{
if (discriminator == null)
{
return nominalType;
}
// note: EnsureKnownTypesAreRegistered handles its own locking so call from outside any lock
EnsureKnownTypesAreRegistered(nominalType);
__configLock.EnterReadLock();
try
{
Type actualType = null;
HashSet hashSet;
if (__discriminators.TryGetValue(discriminator, out hashSet))
{
foreach (var type in hashSet)
{
if (nominalType.GetTypeInfo().IsAssignableFrom(type))
{
if (actualType == null)
{
actualType = type;
}
else
{
string message = string.Format("Ambiguous discriminator '{0}'.", discriminator);
throw new BsonSerializationException(message);
}
}
}
}
if (actualType == null && discriminator.IsString)
{
actualType = TypeNameDiscriminator.GetActualType(discriminator.AsString); // see if it's a Type name
}
if (actualType == null)
{
string message = string.Format("Unknown discriminator value '{0}'.", discriminator);
throw new BsonSerializationException(message);
}
if (!nominalType.GetTypeInfo().IsAssignableFrom(actualType))
{
string message = string.Format(
"Actual type {0} is not assignable to expected type {1}.",
actualType.FullName, nominalType.FullName);
throw new BsonSerializationException(message);
}
return actualType;
}
finally
{
__configLock.ExitReadLock();
}
}
///
/// Looks up the discriminator convention for a type.
///
/// The type.
/// A discriminator convention.
public static IDiscriminatorConvention LookupDiscriminatorConvention(Type type)
{
var typeInfo = type.GetTypeInfo();
__configLock.EnterReadLock();
try
{
IDiscriminatorConvention convention;
if (__discriminatorConventions.TryGetValue(type, out convention))
{
return convention;
}
}
finally
{
__configLock.ExitReadLock();
}
__configLock.EnterWriteLock();
try
{
IDiscriminatorConvention convention;
if (!__discriminatorConventions.TryGetValue(type, out convention))
{
if (type == typeof(object))
{
// if there is no convention registered for object register the default one
convention = new ObjectDiscriminatorConvention("_t");
RegisterDiscriminatorConvention(typeof(object), convention);
}
else if (typeInfo.IsInterface)
{
// TODO: should convention for interfaces be inherited from parent interfaces?
convention = LookupDiscriminatorConvention(typeof(object));
RegisterDiscriminatorConvention(type, convention);
}
else
{
// inherit the discriminator convention from the closest parent (that isn't object) that has one
// otherwise default to the standard hierarchical convention
Type parentType = type.GetTypeInfo().BaseType;
while (convention == null)
{
if (parentType == typeof(object))
{
convention = StandardDiscriminatorConvention.Hierarchical;
break;
}
if (__discriminatorConventions.TryGetValue(parentType, out convention))
{
break;
}
parentType = parentType.GetTypeInfo().BaseType;
}
// register this convention for all types between this and the parent type where we found the convention
var unregisteredType = type;
while (unregisteredType != parentType)
{
RegisterDiscriminatorConvention(unregisteredType, convention);
unregisteredType = unregisteredType.GetTypeInfo().BaseType;
}
}
}
return convention;
}
finally
{
__configLock.ExitWriteLock();
}
}
///
/// Looks up an IdGenerator.
///
/// The Id type.
/// An IdGenerator for the Id type.
public static IIdGenerator LookupIdGenerator(Type type)
{
var typeInfo = type.GetTypeInfo();
__configLock.EnterReadLock();
try
{
IIdGenerator idGenerator;
if (__idGenerators.TryGetValue(type, out idGenerator))
{
return idGenerator;
}
}
finally
{
__configLock.ExitReadLock();
}
__configLock.EnterWriteLock();
try
{
IIdGenerator idGenerator;
if (!__idGenerators.TryGetValue(type, out idGenerator))
{
if (typeInfo.IsValueType && __useZeroIdChecker)
{
var iEquatableDefinition = typeof(IEquatable<>);
var iEquatableType = iEquatableDefinition.MakeGenericType(type);
if (iEquatableType.GetTypeInfo().IsAssignableFrom(type))
{
var zeroIdCheckerDefinition = typeof(ZeroIdChecker<>);
var zeroIdCheckerType = zeroIdCheckerDefinition.MakeGenericType(type);
idGenerator = (IIdGenerator)Activator.CreateInstance(zeroIdCheckerType);
}
}
else if (__useNullIdChecker)
{
idGenerator = NullIdChecker.Instance;
}
else
{
idGenerator = null;
}
__idGenerators[type] = idGenerator; // remember it even if it's null
}
return idGenerator;
}
finally
{
__configLock.ExitWriteLock();
}
}
///
/// Looks up a serializer for a Type.
///
/// The type.
/// A serializer for type T.
public static IBsonSerializer LookupSerializer()
{
return (IBsonSerializer)LookupSerializer(typeof(T));
}
///
/// Looks up a serializer for a Type.
///
/// The Type.
/// A serializer for the Type.
public static IBsonSerializer LookupSerializer(Type type)
{
return __serializerRegistry.GetSerializer(type);
}
///
/// Registers the discriminator for a type.
///
/// The type.
/// The discriminator.
public static void RegisterDiscriminator(Type type, BsonValue discriminator)
{
var typeInfo = type.GetTypeInfo();
if (typeInfo.IsInterface)
{
var message = string.Format("Discriminators can only be registered for classes, not for interface {0}.", type.FullName);
throw new BsonSerializationException(message);
}
__configLock.EnterWriteLock();
try
{
HashSet hashSet;
if (!__discriminators.TryGetValue(discriminator, out hashSet))
{
hashSet = new HashSet();
__discriminators.Add(discriminator, hashSet);
}
if (!hashSet.Contains(type))
{
hashSet.Add(type);
// mark all base types as discriminated (so we know that it's worth reading a discriminator)
for (var baseType = type.GetTypeInfo().BaseType; baseType != null; baseType = baseType.GetTypeInfo().BaseType)
{
__discriminatedTypes.Add(baseType);
}
}
}
finally
{
__configLock.ExitWriteLock();
}
}
///
/// Registers the discriminator convention for a type.
///
/// Type type.
/// The discriminator convention.
public static void RegisterDiscriminatorConvention(Type type, IDiscriminatorConvention convention)
{
__configLock.EnterWriteLock();
try
{
if (!__discriminatorConventions.ContainsKey(type))
{
__discriminatorConventions.Add(type, convention);
}
else
{
var message = string.Format("There is already a discriminator convention registered for type {0}.", type.FullName);
throw new BsonSerializationException(message);
}
}
finally
{
__configLock.ExitWriteLock();
}
}
///
/// Registers a generic serializer definition for a generic type.
///
/// The generic type.
/// The generic serializer definition.
public static void RegisterGenericSerializerDefinition(
Type genericTypeDefinition,
Type genericSerializerDefinition)
{
__typeMappingSerializationProvider.RegisterMapping(genericTypeDefinition, genericSerializerDefinition);
}
///
/// Registers an IdGenerator for an Id Type.
///
/// The Id Type.
/// The IdGenerator for the Id Type.
public static void RegisterIdGenerator(Type type, IIdGenerator idGenerator)
{
__configLock.EnterWriteLock();
try
{
__idGenerators[type] = idGenerator;
}
finally
{
__configLock.ExitWriteLock();
}
}
///
/// Registers a serialization provider.
///
/// The serialization provider.
public static void RegisterSerializationProvider(IBsonSerializationProvider provider)
{
__serializerRegistry.RegisterSerializationProvider(provider);
}
///
/// Registers a serializer for a type.
///
/// The type.
/// The serializer.
public static void RegisterSerializer(IBsonSerializer serializer)
{
RegisterSerializer(typeof(T), serializer);
}
///
/// Registers a serializer for a type.
///
/// The type.
/// The serializer.
public static void RegisterSerializer(Type type, IBsonSerializer serializer)
{
__serializerRegistry.RegisterSerializer(type, serializer);
}
///
/// Serializes a value.
///
/// The nominal type of the object.
/// The BsonWriter.
/// The object.
/// The serialization context configurator.
/// The serialization args.
public static void Serialize(
IBsonWriter bsonWriter,
TNominalType value,
Action configurator = null,
BsonSerializationArgs args = default(BsonSerializationArgs))
{
var serializer = LookupSerializer();
var context = BsonSerializationContext.CreateRoot(bsonWriter, configurator);
serializer.Serialize(context, args, value);
}
///
/// Serializes a value.
///
/// The BsonWriter.
/// The nominal type of the object.
/// The object.
/// The serialization context configurator.
/// The serialization args.
public static void Serialize(
IBsonWriter bsonWriter,
Type nominalType,
object value,
Action configurator = null,
BsonSerializationArgs args = default(BsonSerializationArgs))
{
var serializer = LookupSerializer(nominalType);
var context = BsonSerializationContext.CreateRoot(bsonWriter, configurator);
serializer.Serialize(context, args, value);
}
// internal static methods
internal static void EnsureKnownTypesAreRegistered(Type nominalType)
{
__configLock.EnterReadLock();
try
{
if (__typesWithRegisteredKnownTypes.Contains(nominalType))
{
return;
}
}
finally
{
__configLock.ExitReadLock();
}
__configLock.EnterWriteLock();
try
{
if (!__typesWithRegisteredKnownTypes.Contains(nominalType))
{
// only call LookupClassMap for classes with a BsonKnownTypesAttribute
#if NET452
var knownTypesAttribute = nominalType.GetTypeInfo().GetCustomAttributes(typeof(BsonKnownTypesAttribute), false);
#else
var knownTypesAttribute = nominalType.GetTypeInfo().GetCustomAttributes(typeof(BsonKnownTypesAttribute), false).ToArray();
#endif
if (knownTypesAttribute != null && knownTypesAttribute.Length > 0)
{
// try and force a scan of the known types
LookupSerializer(nominalType);
}
__typesWithRegisteredKnownTypes.Add(nominalType);
}
}
finally
{
__configLock.ExitWriteLock();
}
}
// private static methods
private static void CreateSerializerRegistry()
{
__serializerRegistry = new BsonSerializerRegistry();
__typeMappingSerializationProvider = new TypeMappingSerializationProvider();
// order matters. It's in reverse order of how they'll get consumed
__serializerRegistry.RegisterSerializationProvider(new BsonClassMapSerializationProvider());
__serializerRegistry.RegisterSerializationProvider(new DiscriminatedInterfaceSerializationProvider());
__serializerRegistry.RegisterSerializationProvider(new CollectionsSerializationProvider());
__serializerRegistry.RegisterSerializationProvider(new PrimitiveSerializationProvider());
__serializerRegistry.RegisterSerializationProvider(new AttributedSerializationProvider());
__serializerRegistry.RegisterSerializationProvider(__typeMappingSerializationProvider);
__serializerRegistry.RegisterSerializationProvider(new BsonObjectModelSerializationProvider());
}
private static void RegisterIdGenerators()
{
RegisterIdGenerator(typeof(BsonObjectId), BsonObjectIdGenerator.Instance);
RegisterIdGenerator(typeof(Guid), GuidGenerator.Instance);
RegisterIdGenerator(typeof(ObjectId), ObjectIdGenerator.Instance);
}
}
}