예제 #1
0
        /// <summary>
        /// Resolve keys for certain entity set, this function would be called when key is specified as positional values. E.g. EntitySet('key')
        /// </summary>
        /// <param name="type">Type for current entityset.</param>
        /// <param name="positionalValues">The list of positional values.</param>
        /// <param name="convertFunc">The convert function to be used for value converting.</param>
        /// <returns>The resolved key list.</returns>
        public virtual IEnumerable <KeyValuePair <string, object> > ResolveKeys(IEdmEntityType type, IList <string> positionalValues, Func <IEdmTypeReference, string, object> convertFunc)
        {
            var keyProperties = type.Key().ToList();

            // Throw an error if key size from url doesn't match that from model.
            // Other derived ODataUriResolver intended for alternative key resolution, such as the built in AlternateKeysODataUriResolver,
            // should override this ResolveKeys method.
            if (keyProperties.Count != positionalValues.Count)
            {
                throw ExceptionUtil.CreateBadRequestError(Strings.BadRequest_KeyCountMismatch(type.FullName()));
            }

            var keyPairList = new List <KeyValuePair <string, object> >(positionalValues.Count);

            for (int i = 0; i < keyProperties.Count; i++)
            {
                string       valueText      = positionalValues[i];
                IEdmProperty keyProperty    = keyProperties[i];
                object       convertedValue = convertFunc(keyProperty.Type, valueText);
                if (convertedValue == null)
                {
                    throw ExceptionUtil.CreateSyntaxError();
                }

                keyPairList.Add(new KeyValuePair <string, object>(keyProperty.Name, convertedValue));
            }

            return(keyPairList);
        }
        /// <summary>
        /// Recursively ensures that the maximum count/depth are not exceeded by walking the tree.
        /// </summary>
        /// <param name="expandTree">The expand tree to walk and validate.</param>
        /// <param name="currentDepth">The current depth of the tree walk.</param>
        private void EnsureMaximumCountAndDepthAreNotExceeded(SelectExpandClause expandTree, int currentDepth)
        {
            Debug.Assert(expandTree != null, "expandTree != null");
            if (currentDepth > this.maxDepth)
            {
                throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.UriParser_ExpandDepthExceeded(currentDepth, this.maxDepth));
            }

            foreach (ExpandedNavigationSelectItem expandItem in expandTree.SelectedItems.Where(I => I.GetType() == typeof(ExpandedNavigationSelectItem)))
            {
                this.currentCount++;
                if (this.currentCount > this.maxCount)
                {
                    throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.UriParser_ExpandCountExceeded(this.currentCount, this.maxCount));
                }

                this.EnsureMaximumCountAndDepthAreNotExceeded(expandItem.SelectAndExpand, currentDepth + 1);
            }

            this.currentCount += expandTree.SelectedItems.Where(I => I.GetType() == typeof(ExpandedReferenceSelectItem)).Count();
            if (this.currentCount > this.maxCount)
            {
                throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.UriParser_ExpandCountExceeded(this.currentCount, this.maxCount));
            }
        }
예제 #3
0
        /// <summary>
        /// Resolve keys for certain entity set, this function would be called when key is specified as name value pairs. E.g. EntitySet(ID='key')
        /// </summary>
        /// <param name="type">Type for current entityset.</param>
        /// <param name="namedValues">The dictionary of name value pairs.</param>
        /// <param name="convertFunc">The convert function to be used for value converting.</param>
        /// <returns>The resolved key list.</returns>
        public virtual IEnumerable <KeyValuePair <string, object> > ResolveKeys(IEdmEntityType type, IDictionary <string, string> namedValues, Func <IEdmTypeReference, string, object> convertFunc)
        {
            if (!TryResolveKeys(type, namedValues, convertFunc, out IEnumerable <KeyValuePair <string, object> > resolvedKeys))
            {
                throw ExceptionUtil.CreateBadRequestError(Strings.BadRequest_KeyMismatch(type.FullName()));
            }

            return(resolvedKeys);
        }
예제 #4
0
        /// <summary>
        /// Resolve keys for certain entity set, this function would be called when key is specified as name value pairs. E.g. EntitySet(ID='key')
        /// </summary>
        /// <param name="type">Type for current entityset.</param>
        /// <param name="namedValues">The dictionary of name value pairs.</param>
        /// <param name="convertFunc">The convert function to be used for value converting.</param>
        /// <returns>The resolved key list.</returns>
        public virtual IEnumerable <KeyValuePair <string, object> > ResolveKeys(IEdmEntityType type, IDictionary <string, string> namedValues, Func <IEdmTypeReference, string, object> convertFunc)
        {
            var convertedPairs = new Dictionary <string, object>(StringComparer.Ordinal);
            var keyProperties  = type.Key().ToList();

            // Throw an error if key size from url doesn't match that from model.
            // Other derived ODataUriResolver intended for alternative key resolution, such as the built in AlternateKeysODataUriResolver,
            // should override this ResolveKeys method.
            if (keyProperties.Count != namedValues.Count)
            {
                throw ExceptionUtil.CreateBadRequestError(Strings.BadRequest_KeyCountMismatch(type.FullName()));
            }

            foreach (IEdmStructuralProperty property in keyProperties)
            {
                string valueText;

                if (!namedValues.TryGetValue(property.Name, out valueText))
                {
                    if (EnableCaseInsensitive)
                    {
                        var list = namedValues.Keys.Where(key => string.Equals(property.Name, key, StringComparison.OrdinalIgnoreCase)).ToList();
                        if (list.Count > 1)
                        {
                            throw new ODataException(Strings.UriParserMetadata_MultipleMatchingKeysFound(property.Name));
                        }
                        else if (list.Count == 0)
                        {
                            throw ExceptionUtil.CreateSyntaxError();
                        }

                        valueText = namedValues[list.Single()];
                    }
                    else
                    {
                        throw ExceptionUtil.CreateSyntaxError();
                    }
                }

                object convertedValue = convertFunc(property.Type, valueText);
                if (convertedValue == null)
                {
                    throw ExceptionUtil.CreateSyntaxError();
                }

                convertedPairs[property.Name] = convertedValue;
            }

            return(convertedPairs);
        }
예제 #5
0
        /// <summary>
        /// Resolve keys for certain entity set, this function would be called when key is specified as name value pairs. E.g. EntitySet(ID='key')
        /// </summary>
        /// <param name="type">Type for current entityset.</param>
        /// <param name="namedValues">The dictionary of name value pairs.</param>
        /// <param name="convertFunc">The convert function to be used for value converting.</param>
        /// <returns>The resolved key list.</returns>
        public override IEnumerable <KeyValuePair <string, object> > ResolveKeys(IEdmEntityType type, IDictionary <string, string> namedValues, Func <IEdmTypeReference, string, object> convertFunc)
        {
            if (base.TryResolveKeys(type, namedValues, convertFunc, out IEnumerable <KeyValuePair <string, object> > convertedPairs))
            {
                return(convertedPairs);
            }

            if (!TryResolveAlternateKeys(type, namedValues, convertFunc, out IEnumerable <KeyValuePair <string, object> > alternateConvertedPairs))
            {
                throw ExceptionUtil.CreateBadRequestError(Strings.BadRequest_KeyOrAlternateKeyMismatch(type.FullName()));
            }

            return(alternateConvertedPairs);
        }
예제 #6
0
        /// <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();
            }

            return(new KeySegment(segment, keyPairs, targetEntityType, segment.TargetEdmNavigationSource));
        }
예제 #7
0
        /// <summary>
        /// Try to resolve a function from the given inputs.
        /// </summary>
        /// <param name="identifier">The identifier of the function that we're trying to find</param>
        /// <param name="parameterNames">the names of the parameters to search for.</param>
        /// <param name="model">the model to use to look up the operation import</param>
        /// <param name="matchingOperationImport">The single matching function found.</param>
        /// <param name="resolver">Resolver to be used.</param>
        /// <returns>True if a function was matched, false otherwise. Will throw if the model has illegal operation imports.</returns>
        internal static bool ResolveOperationImportFromList(string identifier, IList <string> parameterNames, IEdmModel model, out IEdmOperationImport matchingOperationImport, ODataUriResolver resolver)
        {
            IEnumerable <IEdmOperationImport> candidateMatchingOperationImports         = null;
            IList <IEdmOperationImport>       foundActionImportsWhenLookingForFunctions = new List <IEdmOperationImport>();

            try
            {
                if (parameterNames.Count > 0)
                {
                    // In this case we have to return a function so filter out actions because the number of parameters > 0.
                    candidateMatchingOperationImports = resolver.ResolveOperationImports(model, identifier)
                                                        .RemoveActionImports(out foundActionImportsWhenLookingForFunctions)
                                                        .FilterOperationsByParameterNames(parameterNames, resolver.EnableCaseInsensitive);
                }
                else
                {
                    candidateMatchingOperationImports = resolver.ResolveOperationImports(model, identifier);
                }
            }
            catch (Exception exc)
            {
                if (!ExceptionUtils.IsCatchableExceptionType(exc))
                {
                    throw;
                }

                throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_FoundInvalidOperationImport(identifier), exc);
            }

            if (foundActionImportsWhenLookingForFunctions.Count > 0)
            {
                throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier));
            }

            // If any of the things returned are an action, it better be the only thing returned, and there can't be parameters in the URL
            if (candidateMatchingOperationImports.Any(f => f.IsActionImport()))
            {
                if (candidateMatchingOperationImports.Count() > 1)
                {
                    if (candidateMatchingOperationImports.Any(o => o.IsFunctionImport()))
                    {
                        throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_MultipleOperationImportOverloads(identifier));
                    }
                    else
                    {
                        throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_MultipleActionImportOverloads(identifier));
                    }
                }

                if (parameterNames.Count() != 0)
                {
                    throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier));
                }

                matchingOperationImport = candidateMatchingOperationImports.Single();
                return(true);
            }

            // If parameter count is zero and there is one function import whose parameter count is zero, return this function import.
            if (candidateMatchingOperationImports.Count() > 1 && parameterNames.Count == 0)
            {
                candidateMatchingOperationImports = candidateMatchingOperationImports.Where(operationImport => operationImport.Operation.Parameters.Count() == 0);
            }

            if (!candidateMatchingOperationImports.HasAny())
            {
                matchingOperationImport = null;
                return(false);
            }

            // If more than one overload matches, try to select based on optional parameters
            if (candidateMatchingOperationImports.Count() > 1)
            {
                candidateMatchingOperationImports = candidateMatchingOperationImports.FindBestOverloadBasedOnParameters(parameterNames);
            }

            if (candidateMatchingOperationImports.Count() > 1)
            {
                throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_MultipleOperationImportOverloads(identifier));
            }

            matchingOperationImport = candidateMatchingOperationImports.Single();

            return(matchingOperationImport != null);
        }
예제 #8
0
        /// <summary>
        /// Try to resolve a function from the given inputs.
        /// </summary>
        /// <param name="identifier">The identifier of the function that we're trying to find</param>
        /// <param name="parameterNames">the names of the parameters to search for.</param>
        /// <param name="bindingType">the type of the previous segment</param>
        /// <param name="model">the model to use to look up the operation import</param>
        /// <param name="matchingOperation">The single matching function found.</param>
        /// <param name="resolver">Resolver to be used.</param>
        /// <returns>True if a function was matched, false otherwise. Will throw if the model has illegal operation imports.</returns>
        internal static bool ResolveOperationFromList(string identifier, IEnumerable <string> parameterNames, IEdmType bindingType, IEdmModel model, out IEdmOperation matchingOperation, ODataUriResolver resolver)
        {
            // TODO: update code that is duplicate between operation and operation import, add more tests.
            // If the previous segment is an open type, the service action name is required to be fully qualified or else we always treat it as an open property name.
            if (bindingType != null)
            {
                // TODO: look up actual container names here?
                // When using extension, there may be function call with unqualified name. So loose the restriction here.
                if (bindingType.IsOpen() && !identifier.Contains(".") && resolver.GetType() == typeof(ODataUriResolver))
                {
                    matchingOperation = null;
                    return(false);
                }
            }

            IEnumerable <IEdmOperation> candidateMatchingOperations = null;

            // The extension method FindBoundOperations & FindOperations call IEdmModel.FindDeclaredBoundOperations which can be implemented by anyone and it could throw any type of exception
            // so catching all of them and simply putting it in the inner exception.
            try
            {
                if (bindingType != null)
                {
                    candidateMatchingOperations = resolver.ResolveBoundOperations(model, identifier, bindingType);
                }
                else
                {
                    candidateMatchingOperations = resolver.ResolveUnboundOperations(model, identifier);
                }
            }
            catch (Exception exc)
            {
                if (ExceptionUtils.IsCatchableExceptionType(exc))
                {
                    throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_FoundInvalidOperation(identifier), exc);
                }

                throw;
            }

            IList <IEdmOperation> foundActionsWhenLookingForFunctions = new List <IEdmOperation>();
            bool hasParameters = parameterNames.Count() > 0;

            if (bindingType != null)
            {
                candidateMatchingOperations.EnsureOperationsBoundWithBindingParameter();
            }

            // If the number of parameters > 0 then this has to be a function as actions can't have parameters on the uri only in the payload. Filter further by parameters in this case, otherwise don't.
            if (hasParameters)
            {
                // can only be a function as only functions have parameters on the uri.
                candidateMatchingOperations = candidateMatchingOperations.RemoveActions(out foundActionsWhenLookingForFunctions)
                                              .FilterOperationsByParameterNames(parameterNames, resolver.EnableCaseInsensitive);
            }
            else if (bindingType != null)
            {
                // Filter out functions with more than one parameter. Actions should not be filtered as the parameters are in the payload not the uri
                candidateMatchingOperations = candidateMatchingOperations.Where(o =>
                                                                                (o.IsFunction() && (o.Parameters.Count() == 1 || o.Parameters.Skip(1).All(p => p is IEdmOptionalParameter))) || o.IsAction());
            }
            else
            {
                // Filter out functions with any parameters
                candidateMatchingOperations = candidateMatchingOperations.Where(o => (o.IsFunction() && !o.Parameters.Any()) || o.IsAction());
            }

            // Only filter if there is more than one and its needed.
            if (candidateMatchingOperations.Count() > 1)
            {
                candidateMatchingOperations = candidateMatchingOperations.FilterBoundOperationsWithSameTypeHierarchyToTypeClosestToBindingType(bindingType);
            }

            // If any of the things returned are an action, it better be the only thing returned, and there can't be parameters in the URL
            if (candidateMatchingOperations.Any(f => f.IsAction()))
            {
                if (candidateMatchingOperations.Count() > 1)
                {
                    if (candidateMatchingOperations.Any(o => o.IsFunction()))
                    {
                        throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_MultipleOperationOverloads(identifier));
                    }
                    else
                    {
                        throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_MultipleActionOverloads(identifier));
                    }
                }

                if (hasParameters)
                {
                    throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier));
                }

                matchingOperation = candidateMatchingOperations.Single();
                return(true);
            }

            if (foundActionsWhenLookingForFunctions.Count > 0)
            {
                throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier));
            }

            // If more than one overload matches, try to select based on optional parameters
            if (candidateMatchingOperations.Count() > 1)
            {
                candidateMatchingOperations = candidateMatchingOperations.FindBestOverloadBasedOnParameters(parameterNames);
            }

            if (candidateMatchingOperations.Count() > 1)
            {
                throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_NoSingleMatchFound(identifier, string.Join(",", parameterNames.ToArray())));
            }

            matchingOperation = candidateMatchingOperations.SingleOrDefault();
            return(matchingOperation != null);
        }
예제 #9
0
        /// <summary>
        /// Try to resolve a function from the given inputs.
        /// </summary>
        /// <param name="identifier">The identifier of the function that we're trying to find</param>
        /// <param name="parameterNames">the names of the parameters to search for.</param>
        /// <param name="bindingType">the type of the previous segment</param>
        /// <param name="model">the model to use to look up the operation import</param>
        /// <param name="matchingOperation">The single matching function found.</param>
        /// <param name="resolver">Resolver to be used.</param>
        /// <returns>True if a function was matched, false otherwise. Will throw if the model has illegal operation imports.</returns>
        internal static bool ResolveOperationFromList(string identifier, IList <string> parameterNames, IEdmType bindingType, IEdmModel model, out IEdmOperation matchingOperation, ODataUriResolver resolver)
        {
            // TODO: update code that is duplicate between operation and operation import, add more tests.
            // If the previous segment is an open type, the service action name is required to be fully qualified or else we always treat it as an open property name.
            matchingOperation = null;
            if (bindingType != null)
            {
                // TODO: look up actual container names here?
                // When using extension, there may be function call with unqualified name. So loose the restriction here.
                if (bindingType.IsOpen() && !identifier.Contains(".") && resolver.GetType() == typeof(ODataUriResolver))
                {
                    return(false);
                }
            }

            IEnumerable <IEdmOperation> operationsFromModel;

            // The extension method FindBoundOperations & FindOperations call IEdmModel.FindDeclaredBoundOperations which can be implemented by anyone and it could throw any type of exception
            // so catching all of them and simply putting it in the inner exception.
            try
            {
                if (bindingType != null)
                {
                    operationsFromModel = resolver.ResolveBoundOperations(model, identifier, bindingType);
                }
                else
                {
                    operationsFromModel = resolver.ResolveUnboundOperations(model, identifier);
                }
            }
            catch (Exception exc)
            {
                if (ExceptionUtils.IsCatchableExceptionType(exc))
                {
                    throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_FoundInvalidOperation(identifier), exc);
                }

                throw;
            }

            bool foundActionsWhenLookingForFunctions;
            // Filters candidates based on the parameter names specified in the uri, removes actions if there were parameters specified in the uri but set the out bool to indicate that.
            // If no parameters specified, then matches based on binding type or matches with operations with no parameters.
            IList <IEdmOperation> candidatesMatchingOperations = operationsFromModel.FilterOperationCandidatesBasedOnParameterList(bindingType, parameterNames, resolver.EnableCaseInsensitive, out foundActionsWhenLookingForFunctions);

            // Only filter if there is more than one and its needed.
            if (candidatesMatchingOperations.Count > 1)
            {
                candidatesMatchingOperations = candidatesMatchingOperations.FilterBoundOperationsWithSameTypeHierarchyToTypeClosestToBindingType(bindingType) as IList <IEdmOperation>;

                // This will be only null when no candidates are left. In that case, we can return false here.
                if (candidatesMatchingOperations == null)
                {
                    if (foundActionsWhenLookingForFunctions)
                    {
                        throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier));
                    }

                    return(false);
                }
            }

            // If any of the candidates are an action, it better be the only thing returned, and there can't be parameters in the URL
            if (ResolveActionFromCandidates(candidatesMatchingOperations, identifier, parameterNames.Count > 0, out matchingOperation))
            {
                return(true);
            }

            if (foundActionsWhenLookingForFunctions)
            {
                throw ExceptionUtil.CreateBadRequestError(ODataErrorStrings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(identifier));
            }

            // If more than one overload matches, try to select based on optional parameters
            if (candidatesMatchingOperations.Count > 1)
            {
                candidatesMatchingOperations = candidatesMatchingOperations.FilterOverloadsBasedOnParameterCount(parameterNames.Count);
            }

            if (candidatesMatchingOperations.Count > 1)
            {
                throw new ODataException(ODataErrorStrings.FunctionOverloadResolver_NoSingleMatchFound(identifier, string.Join(",", parameterNames.ToArray())));
            }

            matchingOperation = candidatesMatchingOperations.Count > 0 ? candidatesMatchingOperations[0] : null;
            return(matchingOperation != null);
        }