/* 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); } } }