/* 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.Linq; using System.Linq.Expressions; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver.Core.Misc; using MongoDB.Driver.Linq; using MongoDB.Driver.Linq.Expressions; using MongoDB.Driver.Linq.Processors; using MongoDB.Driver.Linq.Translators; namespace MongoDB.Driver { /// /// A rendered field. /// public sealed class RenderedFieldDefinition { private readonly string _fieldName; private readonly IBsonSerializer _fieldSerializer; /// /// Initializes a new instance of the class. /// /// The field name. /// The field serializer. public RenderedFieldDefinition(string fieldName, IBsonSerializer fieldSerializer = null) { _fieldName = Ensure.IsNotNull(fieldName, nameof(fieldName)); _fieldSerializer = fieldSerializer; } /// /// Gets the field name. /// public string FieldName { get { return _fieldName; } } /// /// Gets the field serializer. /// public IBsonSerializer FieldSerializer { get { return _fieldSerializer; } } } /// /// A rendered field. /// /// The type of the field. public sealed class RenderedFieldDefinition { private readonly string _fieldName; private readonly IBsonSerializer _fieldSerializer; private readonly IBsonSerializer _underlyingSerializer; private readonly IBsonSerializer _valueSerializer; /// /// Initializes a new instance of the class. /// /// The field name. /// The field serializer. [Obsolete("Use the constructor that takes 4 arguments instead.")] public RenderedFieldDefinition(string fieldName, IBsonSerializer fieldSerializer) : this(fieldName, fieldSerializer, fieldSerializer, fieldSerializer) { } /// /// Initializes a new instance of the class. /// /// The field name. /// The field serializer. /// The value serializer. /// The underlying serializer. public RenderedFieldDefinition(string fieldName, IBsonSerializer fieldSerializer, IBsonSerializer valueSerializer, IBsonSerializer underlyingSerializer) { _fieldName = Ensure.IsNotNull(fieldName, nameof(fieldName)); _fieldSerializer = fieldSerializer; _valueSerializer = Ensure.IsNotNull(valueSerializer, nameof(valueSerializer)); _underlyingSerializer = underlyingSerializer; } /// /// Gets the field name. /// public string FieldName { get { return _fieldName; } } /// /// Gets the field serializer. /// public IBsonSerializer FieldSerializer { get { return _fieldSerializer; } } /// /// Gets the underlying serializer. /// public IBsonSerializer UnderlyingSerializer { get { return _underlyingSerializer; } } /// /// Gets the value serializer. /// public IBsonSerializer ValueSerializer { get { return _valueSerializer; } } } /// /// Base class for field names. /// /// The type of the document. public abstract class FieldDefinition { /// /// Renders the field to a . /// /// The document serializer. /// The serializer registry. /// A . public abstract RenderedFieldDefinition Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry); /// /// Performs an implicit conversion from to . /// /// Name of the field. /// /// The result of the conversion. /// public static implicit operator FieldDefinition(string fieldName) { if (fieldName == null) { return null; } return new StringFieldDefinition(fieldName); } } /// /// Base class for field names. /// /// The type of the document. /// The type of the field. public abstract class FieldDefinition { /// /// Renders the field to a . /// /// The document serializer. /// The serializer registry. /// A . public abstract RenderedFieldDefinition Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry); /// /// Performs an implicit conversion from to . /// /// Name of the field. /// /// The result of the conversion. /// public static implicit operator FieldDefinition(string fieldName) { if (fieldName == null) { return null; } return new StringFieldDefinition(fieldName, null); } /// /// Performs an implicit conversion from to . /// /// The field. /// /// The result of the conversion. /// public static implicit operator FieldDefinition(FieldDefinition field) { return new UntypedFieldDefinitionAdapter(field); } } /// /// An based field. /// /// The type of the document. public sealed class ExpressionFieldDefinition : FieldDefinition { private readonly LambdaExpression _expression; /// /// Initializes a new instance of the class. /// /// The expression. public ExpressionFieldDefinition(LambdaExpression expression) { _expression = Ensure.IsNotNull(expression, nameof(expression)); if (expression.Parameters.Count != 1) { throw new ArgumentException("Only a single parameter lambda expression is allowed.", "expression"); } if (expression.Parameters[0].Type != typeof(TDocument)) { var message = string.Format("The lambda expression parameter must be of type {0}.", typeof(TDocument)); throw new ArgumentException(message, "expression"); } } /// /// Gets the expression. /// public LambdaExpression Expression { get { return _expression; } } /// public override RenderedFieldDefinition Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) { var bindingContext = new PipelineBindingContext(serializerRegistry); var lambda = ExpressionHelper.GetLambda(PartialEvaluator.Evaluate(_expression)); var parameterExpression = new DocumentExpression(documentSerializer); bindingContext.AddExpressionMapping(lambda.Parameters[0], parameterExpression); var bound = bindingContext.Bind(lambda.Body); bound = FieldExpressionFlattener.FlattenFields(bound); IFieldExpression field; if (!ExpressionHelper.TryGetExpression(bound, out field)) { var message = string.Format("Unable to determine the serialization information for {0}.", _expression); throw new InvalidOperationException(message); } return new RenderedFieldDefinition(field.FieldName, field.Serializer); } } /// /// An based field. /// /// The type of the document. /// The type of the field. public sealed class ExpressionFieldDefinition : FieldDefinition { private readonly Expression> _expression; /// /// Initializes a new instance of the class. /// /// The expression. public ExpressionFieldDefinition(Expression> expression) { _expression = Ensure.IsNotNull(expression, nameof(expression)); } /// /// Gets the expression. /// public Expression> Expression { get { return _expression; } } /// public override RenderedFieldDefinition Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) { var lambda = (LambdaExpression)PartialEvaluator.Evaluate(_expression); var bindingContext = new PipelineBindingContext(serializerRegistry); var parameterExpression = new DocumentExpression(documentSerializer); bindingContext.AddExpressionMapping(lambda.Parameters[0], parameterExpression); var bound = bindingContext.Bind(lambda.Body); bound = FieldExpressionFlattener.FlattenFields(bound); IFieldExpression field; if (!Linq.ExpressionHelper.TryGetExpression(bound, out field)) { var message = string.Format("Unable to determine the serialization information for {0}.", _expression); throw new InvalidOperationException(message); } var underlyingSerializer = field.Serializer; var fieldSerializer = underlyingSerializer as IBsonSerializer; var valueSerializer = (IBsonSerializer)FieldValueSerializerHelper.GetSerializerForValueType(underlyingSerializer, serializerRegistry, typeof(TField)); return new RenderedFieldDefinition(field.FieldName, fieldSerializer, valueSerializer, underlyingSerializer); } } /// /// A based field name. /// /// The type of the document. public sealed class StringFieldDefinition : FieldDefinition { private readonly string _fieldName; /// /// Initializes a new instance of the class. /// /// Name of the field. public StringFieldDefinition(string fieldName) { _fieldName = Ensure.IsNotNull(fieldName, nameof(fieldName)); } /// public override RenderedFieldDefinition Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) { string resolvedName; IBsonSerializer resolvedSerializer; StringFieldDefinitionHelper.Resolve(_fieldName, documentSerializer, out resolvedName, out resolvedSerializer); return new RenderedFieldDefinition(resolvedName, resolvedSerializer); } } /// /// A based field name. /// /// The type of the document. /// The type of the field. public sealed class StringFieldDefinition : FieldDefinition { private readonly string _fieldName; private readonly IBsonSerializer _fieldSerializer; /// /// Initializes a new instance of the class. /// /// Name of the field. /// The field serializer. public StringFieldDefinition(string fieldName, IBsonSerializer fieldSerializer = null) { _fieldName = Ensure.IsNotNull(fieldName, nameof(fieldName)); _fieldSerializer = fieldSerializer; } /// public override RenderedFieldDefinition Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) { string resolvedName; IBsonSerializer underlyingSerializer; StringFieldDefinitionHelper.Resolve(_fieldName, documentSerializer, out resolvedName, out underlyingSerializer); var fieldSerializer = underlyingSerializer as IBsonSerializer; IBsonSerializer valueSerializer; if (_fieldSerializer != null) { valueSerializer = _fieldSerializer; } else if (underlyingSerializer != null) { valueSerializer = (IBsonSerializer)FieldValueSerializerHelper.GetSerializerForValueType(underlyingSerializer, serializerRegistry, typeof(TField)); } else { valueSerializer = serializerRegistry.GetSerializer(); } return new RenderedFieldDefinition(resolvedName, fieldSerializer, valueSerializer, underlyingSerializer); } } internal static class StringFieldDefinitionHelper { public static void Resolve(string fieldName, IBsonSerializer serializer, out string resolvedFieldName, out IBsonSerializer resolvedFieldSerializer) { resolvedFieldName = fieldName; resolvedFieldSerializer = null; var documentSerializer = serializer as IBsonDocumentSerializer; if (documentSerializer == null) { return; } // shortcut BsonDocumentSerializer since it is so common if (serializer.GetType() == typeof(BsonDocumentSerializer)) { return; } BsonSerializationInfo serializationInfo; // first, lets try the quick and easy one, which will be a majority of cases if (documentSerializer.TryGetMemberSerializationInfo(fieldName, out serializationInfo)) { resolvedFieldName = serializationInfo.ElementName; resolvedFieldSerializer = serializationInfo.Serializer; return; } // now lets go and do the more difficult variant var nameParts = fieldName.Split('.'); if (nameParts.Length <= 1) { // if we only have 1, then it's no different than what we did above // when we found nothing. return; } IBsonArraySerializer arraySerializer; resolvedFieldSerializer = documentSerializer; for (int i = 0; i < nameParts.Length; i++) { if (nameParts[i] == "$" || nameParts[i].All(char.IsDigit)) { arraySerializer = resolvedFieldSerializer as IBsonArraySerializer; if (arraySerializer != null && arraySerializer.TryGetItemSerializationInfo(out serializationInfo)) { resolvedFieldSerializer = serializationInfo.Serializer; continue; } resolvedFieldSerializer = null; break; } documentSerializer = resolvedFieldSerializer as IBsonDocumentSerializer; if (documentSerializer == null || !documentSerializer.TryGetMemberSerializationInfo(nameParts[i], out serializationInfo)) { // need to check if this is an any element array match arraySerializer = resolvedFieldSerializer as IBsonArraySerializer; if (arraySerializer != null && arraySerializer.TryGetItemSerializationInfo(out serializationInfo)) { documentSerializer = serializationInfo.Serializer as IBsonDocumentSerializer; if (documentSerializer == null || !documentSerializer.TryGetMemberSerializationInfo(nameParts[i], out serializationInfo)) { resolvedFieldSerializer = null; break; } } else { resolvedFieldSerializer = null; break; } } nameParts[i] = serializationInfo.ElementName; resolvedFieldSerializer = serializationInfo.Serializer; } resolvedFieldName = string.Join(".", nameParts); } } internal class UntypedFieldDefinitionAdapter : FieldDefinition { private readonly FieldDefinition _adaptee; public UntypedFieldDefinitionAdapter(FieldDefinition adaptee) { _adaptee = Ensure.IsNotNull(adaptee, nameof(adaptee)); } public override RenderedFieldDefinition Render(IBsonSerializer documentSerializer, IBsonSerializerRegistry serializerRegistry) { var rendered = _adaptee.Render(documentSerializer, serializerRegistry); return new RenderedFieldDefinition(rendered.FieldName, rendered.UnderlyingSerializer); } } }