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