Exemple #1
0
        /// <summary>
        /// Find any related keys from the parent key segment, if it exists, and add them to the raw key values that
        /// we already have from the uri.
        /// </summary>
        /// <param name="rawKeyValuesFromUri">The raw key values as we've parsed them from the uri.</param>
        /// <param name="targetEntityKeyProperties">The list of key properties on the target entity.</param>
        /// <param name="currentNavigationProperty">The current navigation property that we're trying to follow using the raw key values</param>
        /// <param name="keySegmentOfParentEntity">The key segment of the parent entity in this path, if it exists. Null otherwise</param>
        /// <returns>A new SegmentArgumentParser with any keys that were found added to its list of NamedValues.</returns>
        /// <throws>Thorws if the input currentNavigationProperty is null.</throws>
        public static SegmentArgumentParser FindAndUseKeysFromRelatedSegment(SegmentArgumentParser rawKeyValuesFromUri, IEnumerable <IEdmStructuralProperty> targetEntityKeyProperties, IEdmNavigationProperty currentNavigationProperty, KeySegment keySegmentOfParentEntity)
        {
            ExceptionUtils.CheckArgumentNotNull(currentNavigationProperty, "currentNavigationProperty");
            ExceptionUtils.CheckArgumentNotNull(rawKeyValuesFromUri, "rawKeyValuesFromUri");

            ReadOnlyCollection <IEdmStructuralProperty> targetKeyPropertyList = targetEntityKeyProperties != null ? new ReadOnlyCollection <IEdmStructuralProperty>(targetEntityKeyProperties.ToList()) : new ReadOnlyCollection <IEdmStructuralProperty>(new List <IEdmStructuralProperty>());

            // should only get here if the number of raw parameters from the uri is different than the number of key properties for the target entity.
            Debug.Assert(rawKeyValuesFromUri.ValueCount < targetKeyPropertyList.Count(), "rawKeyValuesFromUri.ValueCount < targetEntityKeyProperties.Count()");

            // if the raw key from the uri has positional values, there must be only one of them
            // its important to cache this value here because we'll change it when we add new
            // named values below (the implementation of AreValuesNamed is just namedValues !=null)
            bool hasPositionalValues = !rawKeyValuesFromUri.AreValuesNamed;

            if (hasPositionalValues && rawKeyValuesFromUri.ValueCount > 1)
            {
                return(rawKeyValuesFromUri);
            }

            if (keySegmentOfParentEntity == null)
            {
                return(rawKeyValuesFromUri);
            }

            // TODO: p2 merge the below 2 pieces of codes
            // find out if any target entity key properties have referential constraints that link them to the previous rawKeyValuesFromUri.
            List <EdmReferentialConstraintPropertyPair> keysFromReferentialIntegrityConstraint = ExtractMatchingPropertyPairsFromNavProp(currentNavigationProperty, targetKeyPropertyList).ToList();

            foreach (EdmReferentialConstraintPropertyPair keyFromReferentialIntegrityConstraint in keysFromReferentialIntegrityConstraint)
            {
                KeyValuePair <string, object> valueFromParent = keySegmentOfParentEntity.Keys.SingleOrDefault(x => x.Key == keyFromReferentialIntegrityConstraint.DependentProperty.Name);
                if (valueFromParent.Key != null)
                {
                    // if the key from the referential integrity constraint is one of the target key properties
                    // and that key property isn't already populated in the raw key values from the uri, then
                    // we set that value to the value from the parent key segment.
                    if (targetKeyPropertyList.Any(x => x.Name == keyFromReferentialIntegrityConstraint.PrincipalProperty.Name))
                    {
                        rawKeyValuesFromUri.AddNamedValue(
                            keyFromReferentialIntegrityConstraint.PrincipalProperty.Name,
                            valueFromParent.Value.ToString());
                    }
                }
            }

            // also need to look to see if any nav props exist in the target set that refer back to this same set, which might have
            // referential constraints also.
            keysFromReferentialIntegrityConstraint.Clear();
            IEdmNavigationProperty reverseNavProp = currentNavigationProperty.Partner;

            if (reverseNavProp != null)
            {
                keysFromReferentialIntegrityConstraint.AddRange(ExtractMatchingPropertyPairsFromReversedNavProp(reverseNavProp, targetKeyPropertyList));
            }

            foreach (EdmReferentialConstraintPropertyPair keyFromReferentialIntegrityConstraint in keysFromReferentialIntegrityConstraint)
            {
                KeyValuePair <string, object> valueFromParent = keySegmentOfParentEntity.Keys.SingleOrDefault(x => x.Key == keyFromReferentialIntegrityConstraint.PrincipalProperty.Name);
                if (valueFromParent.Key != null)
                {
                    // if the key from the referential integrity constraint is one of the target key properties
                    // and that key property isn't already populated in the raw key values from the uri, then
                    // we set that value to the value from the parent key segment.
                    if (targetKeyPropertyList.Any(x => x.Name == keyFromReferentialIntegrityConstraint.DependentProperty.Name))
                    {
                        rawKeyValuesFromUri.AddNamedValue(
                            keyFromReferentialIntegrityConstraint.DependentProperty.Name,
                            valueFromParent.Value.ToString());
                    }
                }
            }

            // if we had a positional value before, then we need to add that value as a new named value.
            // the name that we choose will be the only value from the target entity key properties
            // that isn't already set in the NamedValues list.
            if (hasPositionalValues)
            {
                if (rawKeyValuesFromUri.NamedValues != null)
                {
                    List <IEdmStructuralProperty> unassignedProperties = targetKeyPropertyList.Where(x => !rawKeyValuesFromUri.NamedValues.ContainsKey(x.Name)).ToList();

                    if (unassignedProperties.Count == 1)
                    {
                        rawKeyValuesFromUri.AddNamedValue(unassignedProperties[0].Name, rawKeyValuesFromUri.PositionalValues[0]);
                    }
                    else
                    {
                        return(rawKeyValuesFromUri);
                    }

                    // clear out the positional value so that we keep a consistent state in the
                    // raw keys from uri.
                    rawKeyValuesFromUri.PositionalValues.Clear();
                }
                else
                {
                    return(rawKeyValuesFromUri);
                }
            }

            return(rawKeyValuesFromUri);
        }
Exemple #2
0
        /// <summary>Attempts to parse key values from the specified text.</summary>
        /// <param name='text'>Text to parse (not null).</param>
        /// <param name="allowNamedValues">Set to true if the parser should accept named values
        ///     so syntax like Name='value'. If this is false, the parsing will fail on such constructs.</param>
        /// <param name="allowNull">Set to true if the parser should accept null values.
        ///     If set to false, the parser will fail on null values.</param>
        /// <param name='instance'>After invocation, the parsed key instance.</param>
        /// <param name="enableUriTemplateParsing">Whether Uri template parsing is enabled.</param>
        /// <returns>
        /// true if the key instance was parsed; false if there was a
        /// syntactic error.
        /// </returns>
        /// <remarks>
        /// The returned instance contains only string values. To get typed values, a call to
        /// TryConvertValues is necessary.
        /// </remarks>
        private static bool TryParseFromUri(string text, bool allowNamedValues, bool allowNull, out SegmentArgumentParser instance, bool enableUriTemplateParsing)
        {
            Debug.Assert(text != null, "text != null");

            Dictionary <string, string> namedValues = null;
            List <string> positionalValues          = null;

            // parse keys just like function parameters
            ExpressionLexer          lexer      = new ExpressionLexer("(" + text + ")", true, false);
            UriQueryExpressionParser exprParser = new UriQueryExpressionParser(ODataUriParserSettings.DefaultFilterLimit /* default limit for parsing key value */, lexer);
            var tmp = (new FunctionCallParser(lexer, exprParser)).ParseArgumentListOrEntityKeyList();

            if (lexer.CurrentToken.Kind != ExpressionTokenKind.End)
            {
                instance = null;
                return(false);
            }

            if (tmp.Length == 0)
            {
                instance = Empty;
                return(true);
            }

            string valueText = null;

            foreach (FunctionParameterToken t in tmp)
            {
                valueText = null;
                LiteralToken literalToken = t.ValueToken as LiteralToken;
                if (literalToken != null)
                {
                    valueText = literalToken.OriginalText;

                    // disallow "{...}" if enableUriTemplateParsing is false (which could have been seen as valid function parameter, e.g. array notation)
                    if (!enableUriTemplateParsing && UriTemplateParser.IsValidTemplateLiteral(valueText))
                    {
                        instance = null;
                        return(false);
                    }
                }
                else
                {
                    DottedIdentifierToken dottedIdentifierToken = t.ValueToken as DottedIdentifierToken; // for enum
                    if (dottedIdentifierToken != null)
                    {
                        valueText = dottedIdentifierToken.Identifier;
                    }
                }

                if (valueText != null)
                {
                    if (t.ParameterName == null)
                    {
                        if (namedValues != null)
                        {
                            instance = null; // We cannot mix named and non-named values.
                            return(false);
                        }

                        CreateIfNull(ref positionalValues);
                        positionalValues.Add(valueText);
                    }
                    else
                    {
                        if (positionalValues != null)
                        {
                            instance = null; // We cannot mix named and non-named values.
                            return(false);
                        }

                        CreateIfNull(ref namedValues);
                        namedValues.Add(t.ParameterName, valueText);
                    }
                }
                else
                {
                    instance = null;
                    return(false);
                }
            }

            instance = new SegmentArgumentParser(namedValues, positionalValues, false, enableUriTemplateParsing);
            return(true);
        }
Exemple #3
0
 /// <summary>Attempts to parse nullable values (only positional values, no name-value pairs) from the specified text.</summary>
 /// <param name='text'>Text to parse (not null).</param>
 /// <param name='instance'>After invocation, the parsed key instance.</param>
 /// <returns>
 /// true if the given values were parsed; false if there was a
 /// syntactic error.
 /// </returns>
 /// <remarks>
 /// The returned instance contains only string values. To get typed values, a call to
 /// TryConvertValues is necessary.
 /// </remarks>
 public static bool TryParseNullableTokens(string text, out SegmentArgumentParser instance)
 {
     return(TryParseFromUri(text, false /*allowNamedValues*/, true /*allowNull*/, out instance, false));
 }
Exemple #4
0
 /// <summary>Attempts to parse key values from the specified text.</summary>
 /// <param name='text'>Text to parse (not null).</param>
 /// <param name='instance'>After invocation, the parsed key instance.</param>
 /// <param name="enableUriTemplateParsing">Whether Uri template parsing is enabled.</param>
 /// <returns>
 /// true if the key instance was parsed; false if there was a
 /// syntactic error.
 /// </returns>
 /// <remarks>
 /// The returned instance contains only string values. To get typed values, a call to
 /// TryConvertValues is necessary.
 /// </remarks>
 public static bool TryParseKeysFromUri(string text, out SegmentArgumentParser instance, bool enableUriTemplateParsing)
 {
     return(TryParseFromUri(text, true /*allowNamedValues*/, false /*allowNull*/, out instance, enableUriTemplateParsing));
 }
        /// <summary>
        /// Parses the key properties based on the segment's target type, then creates a new segment for the key.
        /// </summary>
        /// <param name="segment">The segment to apply the key to.</param>
        /// <param name="previousKeySegment">The parent node's key segment.</param>
        /// <param name="key">The key to apply.</param>
        /// <param name="resolver">The resolver to use.</param>
        /// <returns>The newly created key segment.</returns>
        private static KeySegment CreateKeySegment(ODataPathSegment segment, KeySegment previousKeySegment, SegmentArgumentParser key, ODataUriResolver resolver)
        {
            Debug.Assert(segment != null, "segment != null");
            Debug.Assert(key != null && !key.IsEmpty, "key != null && !key.IsEmpty");
            Debug.Assert(segment.SingleResult == false, "segment.SingleResult == false");

            IEdmEntityType targetEntityType = null;

            if (!(segment.TargetEdmType != null && segment.TargetEdmType.IsEntityOrEntityCollectionType(out targetEntityType)))
            {
                throw ExceptionUtil.CreateSyntaxError();
            }

            Debug.Assert(targetEntityType != null, "targetEntityType != null");

            // Make sure the keys specified in the uri matches with the number of keys in the metadata
            var keyProperties = targetEntityType.Key().ToList();

            if (keyProperties.Count != key.ValueCount)
            {
                NavigationPropertySegment currentNavPropSegment = segment as NavigationPropertySegment;
                if (currentNavPropSegment != null)
                {
                    key = KeyFinder.FindAndUseKeysFromRelatedSegment(key, keyProperties, currentNavPropSegment.NavigationProperty, previousKeySegment);
                }

                // if we still didn't find any keys, then throw an error.
                if (keyProperties.Count != key.ValueCount && resolver.GetType() == typeof(ODataUriResolver))
                {
                    throw ExceptionUtil.CreateBadRequestError(ErrorStrings.BadRequest_KeyCountMismatch(targetEntityType.FullName()));
                }
            }

            if (!key.AreValuesNamed && key.ValueCount > 1 && resolver.GetType() == typeof(ODataUriResolver))
            {
                throw ExceptionUtil.CreateBadRequestError(ErrorStrings.RequestUriProcessor_KeysMustBeNamed);
            }

            IEnumerable <KeyValuePair <string, object> > keyPairs;

            if (!key.TryConvertValues(targetEntityType, out keyPairs, resolver))
            {
                throw ExceptionUtil.CreateSyntaxError();
            }

            IEdmEntityType entityType;
            bool           isEntity = segment.TargetEdmType.IsEntityOrEntityCollectionType(out entityType);

            Debug.Assert(isEntity, "Key target type should be an entity type.");

            var keySegment = new KeySegment(keyPairs, entityType, segment.TargetEdmNavigationSource);

            keySegment.CopyValuesFrom(segment);
            keySegment.SingleResult = true;

            return(keySegment);
        }
        /// <summary>Attempts to parse key values from the specified text.</summary>
        /// <param name='text'>Text to parse (not null).</param>
        /// <param name="allowNamedValues">Set to true if the parser should accept named values
        ///     so syntax like Name='value'. If this is false, the parsing will fail on such constructs.</param>
        /// <param name="allowNull">Set to true if the parser should accept null values.
        ///     If set to false, the parser will fail on null values.</param>
        /// <param name='instance'>After invocation, the parsed key instance.</param>
        /// <param name="enableUriTemplateParsing">Whether Uri template parsing is enabled.</param>
        /// <returns>
        /// true if the key instance was parsed; false if there was a
        /// syntactic error.
        /// </returns>
        /// <remarks>
        /// The returned instance contains only string values. To get typed values, a call to
        /// TryConvertValues is necessary.
        /// </remarks>
        private static bool TryParseFromUri(string text, bool allowNamedValues, bool allowNull, out SegmentArgumentParser instance, bool enableUriTemplateParsing)
        {
            Debug.Assert(text != null, "text != null");

            Dictionary <string, string> namedValues = null;
            List <string>   positionalValues        = null;
            ExpressionLexer lexer        = new ExpressionLexer(text, true, false);
            ExpressionToken currentToken = lexer.CurrentToken;

            if (currentToken.Kind == ExpressionTokenKind.End)
            {
                instance = Empty;
                return(true);
            }

            instance = null;
            do
            {
                if (currentToken.Kind == ExpressionTokenKind.Identifier && allowNamedValues)
                {
                    // Name-value pair.
                    if (positionalValues != null)
                    {
                        // We cannot mix named and non-named values.
                        return(false);
                    }

                    string identifier = lexer.CurrentToken.GetIdentifier();
                    lexer.NextToken();
                    if (lexer.CurrentToken.Kind != ExpressionTokenKind.Equal)
                    {
                        return(false);
                    }

                    lexer.NextToken();
                    if (!IsKeyValueToken(lexer.CurrentToken, enableUriTemplateParsing))
                    {
                        return(false);
                    }

                    string namedValue = lexer.CurrentToken.Text;
                    CreateIfNull(ref namedValues);
                    if (namedValues.ContainsKey(identifier))
                    {
                        // Duplicate name.
                        return(false);
                    }

                    namedValues.Add(identifier, namedValue);
                }
                else if ((IsKeyValueToken(currentToken, enableUriTemplateParsing) || (allowNull && currentToken.Kind == ExpressionTokenKind.NullLiteral)))
                {
                    // Positional value.
                    if (namedValues != null)
                    {
                        // We cannot mix named and non-named values.
                        return(false);
                    }

                    CreateIfNull(ref positionalValues);
                    positionalValues.Add(lexer.CurrentToken.Text);
                }
                else
                {
                    return(false);
                }

                // Read the next token. We should be at the end, or find
                // we have a comma followed by something.
                lexer.NextToken();
                currentToken = lexer.CurrentToken;
                if (currentToken.Kind == ExpressionTokenKind.Comma)
                {
                    lexer.NextToken();
                    currentToken = lexer.CurrentToken;
                    if (currentToken.Kind == ExpressionTokenKind.End)
                    {
                        // Trailing comma.
                        return(false);
                    }
                }
            }while (currentToken.Kind != ExpressionTokenKind.End);

            instance = new SegmentArgumentParser(namedValues, positionalValues, false, enableUriTemplateParsing);
            return(true);
        }