/// <summary>
 /// Creates an instance of this class to parse options.
 /// </summary>
 /// <param name="resolver">The URI resolver which will resolve different kinds of Uri parsing context</param>
 /// <param name="parentStructuredType">The parent structured type for expand option</param>
 /// <param name="maxRecursionDepth">Max recursion depth left.</param>
 /// <param name="enableCaseInsensitiveBuiltinIdentifier">Whether to allow case insensitive for builtin identifier.</param>
 /// <param name="enableNoDollarQueryOptions">Whether to enable no dollar query options.</param>
 internal SelectExpandOptionParser(
     ODataUriResolver resolver,
     IEdmStructuredType parentStructuredType,
     int maxRecursionDepth,
     bool enableCaseInsensitiveBuiltinIdentifier = false,
     bool enableNoDollarQueryOptions             = false)
     : this(maxRecursionDepth, enableCaseInsensitiveBuiltinIdentifier, enableNoDollarQueryOptions)
 {
     this.resolver             = resolver;
     this.parentStructuredType = parentStructuredType;
 }
Пример #2
0
 /// <summary>
 /// Build the ExpandOption strategy (SelectOption build does not need resolover and parentEntityType now).
 /// </summary>
 /// <param name="resolver">the URI resolver which will resolve different kinds of Uri parsing context</param>
 /// <param name="clauseToParse">the clause to parse</param>
 /// <param name="parentEntityType">the parent entity type for expand option</param>
 /// <param name="maxRecursiveDepth">max recursive depth</param>
 /// <param name="enableCaseInsensitiveBuiltinIdentifier">Whether to allow case insensitive for builtin identifier.</param>
 /// <param name="enableNoDollarQueryOptions">Whether to enable no dollar query options.</param>
 public SelectExpandParser(
     ODataUriResolver resolver,
     string clauseToParse,
     IEdmStructuredType parentEntityType,
     int maxRecursiveDepth,
     bool enableCaseInsensitiveBuiltinIdentifier = false,
     bool enableNoDollarQueryOptions             = false)
     : this(clauseToParse, maxRecursiveDepth, enableCaseInsensitiveBuiltinIdentifier, enableNoDollarQueryOptions)
 {
     this.resolver         = resolver;
     this.parentEntityType = parentEntityType;
 }
Пример #3
0
        public override IDictionary <IEdmOperationParameter, SingleValueNode> ResolveOperationParameters(IEdmOperation operation, IDictionary <string, SingleValueNode> input)
        {
            Dictionary <IEdmOperationParameter, SingleValueNode> result = new Dictionary <IEdmOperationParameter, SingleValueNode>(EqualityComparer <IEdmOperationParameter> .Default);

            foreach (var item in input)
            {
                IEdmOperationParameter functionParameter = null;
                if (EnableCaseInsensitive)
                {
                    functionParameter = ODataUriResolver.ResolveOpearationParameterNameCaseInsensitive(operation, item.Key);
                }
                else
                {
                    functionParameter = operation.FindParameter(item.Key);
                }

                // ensure parameter name existis
                if (functionParameter == null)
                {
                    throw new ODataException(Strings.ODataParameterWriterCore_ParameterNameNotFoundInOperation(item.Key, operation.Name));
                }

                SingleValueNode newVal = item.Value;

                if (functionParameter.Type.IsEnum() &&
                    newVal is ConstantNode &&
                    newVal.TypeReference != null &&
                    newVal.TypeReference.IsString())
                {
                    string            text = ((ConstantNode)item.Value).Value as string;
                    ODataEnumValue    val;
                    IEdmTypeReference typeRef = functionParameter.Type;

                    if (TryParseEnum(typeRef.Definition as IEdmEnumType, text, out val))
                    {
                        newVal = new ConstantNode(val, text, typeRef);
                    }
                }

                result.Add(functionParameter, newVal);
            }

            return(result);
        }
Пример #4
0
        /// <summary>
        /// Initializes a new instance of <see cref="ODataUriParserConfiguration"/>.
        /// </summary>
        /// <param name="model">Model to use for metadata binding.</param>
        /// <param name="container">The optional dependency injection container to get related services for URI parsing.</param>
        /// <exception cref="System.ArgumentNullException">Throws if input model is null.</exception>
        /// <exception cref="ArgumentException">Throws if the input serviceRoot is not an AbsoluteUri</exception>
        public ODataUriParserConfiguration(IEdmModel model, IServiceProvider container)
        {
            ExceptionUtils.CheckArgumentNotNull(model, "model");

            this.Model           = model;
            this.Container       = container;
            this.Resolver        = ODataUriResolver.GetUriResolver(container);
            this.urlKeyDelimiter = ODataUrlKeyDelimiter.GetODataUrlKeyDelimiter(container);

            if (this.Container == null)
            {
                this.Settings = new ODataUriParserSettings();
            }
            else
            {
                this.Settings = this.Container.GetRequiredService <ODataUriParserSettings>();
            }

            this.EnableUriTemplateParsing = false;
        }
Пример #5
0
        /// <summary>
        /// Try to bind an identifier to a EnumNode
        /// </summary>
        /// <param name="identifier">the identifier to bind</param>
        /// <param name="typeReference">the enum typeReference</param>
        /// <param name="modelWhenNoTypeReference">the current model when no enum typeReference.</param>
        /// <param name="resolver">ODataUriResolver .</param>
        /// <param name="boundEnum">an enum node .</param>
        /// <returns>true if we bound an enum for this token.</returns>
        internal static bool TryBindIdentifier(string identifier, IEdmEnumTypeReference typeReference, IEdmModel modelWhenNoTypeReference, ODataUriResolver resolver, out QueryNode boundEnum)
        {
            boundEnum = null;
            string text = identifier;

            // parse the string, e.g., NS.Color'Green'
            // get type information, and also convert Green into an ODataEnumValue

            // find the first ', before that, it is namespace.type
            int indexOfSingleQuote = text.IndexOf('\'');

            if (indexOfSingleQuote < 0)
            {
                return(false);
            }

            string namespaceAndType = text.Substring(0, indexOfSingleQuote);

            Debug.Assert((typeReference == null) || (modelWhenNoTypeReference == null), "((typeReference == null) || (modelWhenNoTypeReference == null)");

            // validate typeReference but allow type name not found in model for delayed throwing.
            if ((typeReference != null) && !string.Equals(namespaceAndType, typeReference.FullName(), StringComparison.Ordinal))
            {
                return(false);
            }

            // get the type
            IEdmEnumType enumType = typeReference != null
                ?
                                    (IEdmEnumType)typeReference.Definition
                :
                                    UriEdmHelpers.FindEnumTypeFromModel(modelWhenNoTypeReference, namespaceAndType, resolver);

            if (enumType == null)
            {
                return(false);
            }

            // now, find out the value
            UriParserHelper.TryRemovePrefix(namespaceAndType, ref text);
            UriParserHelper.TryRemoveQuotes(ref text);

            // parse string or int value to edm enum value
            string         enumValueString = text;
            ODataEnumValue enumValue;

            if (!TryParseEnum(enumType, enumValueString, out enumValue))
            {
                return(false);
            }

            // create an enum node, enclosing an odata enum value
            IEdmEnumTypeReference enumTypeReference = typeReference ?? new EdmEnumTypeReference(enumType, false);

            boundEnum = new ConstantNode(enumValue, identifier, enumTypeReference);

            return(true);
        }
Пример #6
0
        /// <summary>
        /// This is temp work around for $filter $orderby parameter expression which contains complex or collection
        ///     like "Fully.Qualified.Namespace.CanMoveToAddresses(addresses=[{\"Street\":\"NE 24th St.\",\"City\":\"Redmond\"},{\"Street\":\"Pine St.\",\"City\":\"Seattle\"}])";
        /// TODO:  $filter $orderby parameter expression which contains nested complex or collection should NOT be supported in this way
        ///     but should be parsed into token tree, and binded to node tree: parsedParameters.Select(p => this.bindMethod(p));
        /// </summary>
        /// <param name="model">The model.</param>
        /// <param name="operation">IEdmFunction or IEdmOperation</param>
        /// <param name="parameterTokens">The tokens to bind.</param>
        /// <param name="enableCaseInsensitive">Whether to enable case-insensitive when resolving parameter name.</param>
        /// <param name="enableUriTemplateParsing">Whether Uri template parsing is enabled.</param>
        /// <returns>The FunctionParameterTokens with complex or collection values converted from string like "{...}", or "[..,..,..]".</returns>
        private static ICollection <FunctionParameterToken> HandleComplexOrCollectionParameterValueIfExists(IEdmModel model, IEdmOperation operation, ICollection <FunctionParameterToken> parameterTokens, bool enableCaseInsensitive, bool enableUriTemplateParsing = false)
        {
            ICollection <FunctionParameterToken> partiallyParsedParametersWithComplexOrCollection = new Collection <FunctionParameterToken>();

            foreach (FunctionParameterToken paraToken in parameterTokens)
            {
                FunctionParameterToken funcParaToken;
                IEdmOperationParameter functionParameter = operation.FindParameter(paraToken.ParameterName);
                if (enableCaseInsensitive && functionParameter == null)
                {
                    functionParameter = ODataUriResolver.ResolveOperationParameterNameCaseInsensitive(operation, paraToken.ParameterName);

                    // The functionParameter can not be null here, else this method won't be called.
                    funcParaToken = new FunctionParameterToken(functionParameter.Name, paraToken.ValueToken);
                }
                else
                {
                    funcParaToken = paraToken;
                }

                FunctionParameterAliasToken aliasToken = funcParaToken.ValueToken as FunctionParameterAliasToken;
                if (aliasToken != null)
                {
                    aliasToken.ExpectedParameterType = functionParameter.Type;
                }

                LiteralToken valueToken = funcParaToken.ValueToken as LiteralToken;
                string       valueStr   = null;
                if (valueToken != null && (valueStr = valueToken.Value as string) != null && !string.IsNullOrEmpty(valueToken.OriginalText))
                {
                    ExpressionLexer lexer = new ExpressionLexer(valueToken.OriginalText, true /*moveToFirstToken*/, false /*useSemicolonDelimiter*/, true /*parsingFunctionParameters*/);
                    if (lexer.CurrentToken.Kind == ExpressionTokenKind.BracketedExpression || lexer.CurrentToken.Kind == ExpressionTokenKind.BracedExpression)
                    {
                        object result;
                        UriTemplateExpression expression;

                        if (enableUriTemplateParsing && UriTemplateParser.TryParseLiteral(lexer.CurrentToken.Text, functionParameter.Type, out expression))
                        {
                            result = expression;
                        }
                        else if (!functionParameter.Type.IsStructured() && !functionParameter.Type.IsStructuredCollectionType())
                        {
                            // ExpressionTokenKind.BracketedExpression means text like [1,2]
                            // so now try convert it to collection type value:
                            result = ODataUriUtils.ConvertFromUriLiteral(valueStr, ODataVersion.V4, model, functionParameter.Type);
                        }
                        else
                        {
                            // For complex & colleciton of complex directly return the raw string.
                            partiallyParsedParametersWithComplexOrCollection.Add(funcParaToken);
                            continue;
                        }

                        LiteralToken           newValueToken    = new LiteralToken(result, valueToken.OriginalText);
                        FunctionParameterToken newFuncParaToken = new FunctionParameterToken(funcParaToken.ParameterName, newValueToken);
                        partiallyParsedParametersWithComplexOrCollection.Add(newFuncParaToken);
                        continue;
                    }
                }

                partiallyParsedParametersWithComplexOrCollection.Add(funcParaToken);
            }

            return(partiallyParsedParametersWithComplexOrCollection);
        }
Пример #7
0
        /// <summary>
        /// Tries to bind a given token as a declared annotation term.
        /// </summary>
        /// <param name="tokenIn">Token to bind.</param>
        /// <param name="model">The model to search for this term</param>
        /// <param name="resolver">Resolver for uri parser.</param>
        /// <param name="segment">Bound segment if the token was bound to a declared term successfully, or null.</param>
        /// <returns>True if the token was bound successfully, or false otherwise.</returns>
        private static bool TryBindAsDeclaredTerm(PathSegmentToken tokenIn, IEdmModel model, ODataUriResolver resolver, out ODataPathSegment segment)
        {
            if (!UriParserHelper.IsAnnotation(tokenIn.Identifier))
            {
                segment = null;
                return(false);
            }

            IEdmTerm term = resolver.ResolveTerm(model, tokenIn.Identifier.Remove(0, 1));

            if (term == null)
            {
                segment = null;
                return(false);
            }

            segment = new AnnotationSegment(term);
            return(true);
        }
Пример #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="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);
        }
Пример #9
0
        /// <summary>Tries to create a key segment for the given filter if it is non empty.</summary>
        /// <param name="previous">Segment on which to compose.</param>
        /// <param name="previousKeySegment">The parent node's key segment.</param>
        /// <param name="parenthesisExpression">Parenthesis expression of segment.</param>
        /// <param name="resolver">The resolver to use.</param>
        /// <param name="keySegment">The key segment that was created if the key was non-empty.</param>
        /// <param name="enableUriTemplateParsing">Whether Uri template parsing is enabled.</param>
        /// <returns>Whether the key was non-empty.</returns>
        internal static bool TryCreateKeySegmentFromParentheses(ODataPathSegment previous, KeySegment previousKeySegment, string parenthesisExpression, ODataUriResolver resolver, out ODataPathSegment keySegment, bool enableUriTemplateParsing = false)
        {
            Debug.Assert(parenthesisExpression != null, "parenthesisExpression != null");
            Debug.Assert(previous != null, "segment!= null");
            Debug.Assert(resolver != null, "resolver != null");

            if (previous.SingleResult)
            {
                throw ExceptionUtil.CreateSyntaxError();
            }

            SegmentArgumentParser key;

            if (!SegmentArgumentParser.TryParseKeysFromUri(parenthesisExpression, out key, enableUriTemplateParsing))
            {
                throw ExceptionUtil.CreateSyntaxError();
            }

            // People/NS.Employees() is OK, just like People() is OK
            if (key.IsEmpty)
            {
                keySegment = null;
                return(false);
            }

            keySegment = CreateKeySegment(previous, previousKeySegment, key, resolver);
            return(true);
        }
Пример #10
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));
        }
Пример #11
0
        /// <summary>Tries to convert values to the keys of the specified type.</summary>
        /// <param name="targetEntityType">The specified type.</param>
        /// <param name="keyPairs">The converted key-value pairs.</param>
        /// <param name="resolver">The resolver to use.</param>
        /// <returns>true if all values were converted; false otherwise.</returns>
        public bool TryConvertValues(IEdmEntityType targetEntityType, out IEnumerable <KeyValuePair <string, object> > keyPairs, ODataUriResolver resolver)
        {
            Debug.Assert(!this.IsEmpty, "!this.IsEmpty -- caller should check");
            Debug.Assert(targetEntityType.Key().Count() == this.ValueCount || resolver.GetType() != typeof(ODataUriResolver), "type.KeyProperties.Count == this.ValueCount -- will change with containment");

            if (this.NamedValues != null)
            {
                keyPairs = resolver.ResolveKeys(targetEntityType, this.NamedValues, this.ConvertValueWrapper);
            }
            else
            {
                Debug.Assert(this.positionalValues != null, "positionalValues != null -- otherwise this is Empty");
                Debug.Assert(this.PositionalValues.Count == targetEntityType.Key().Count() || resolver.GetType() != typeof(ODataUriResolver), "Count of positional values does not match.");
                keyPairs = resolver.ResolveKeys(targetEntityType, this.PositionalValues, this.ConvertValueWrapper);
            }

            return(true);
        }
Пример #12
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);
        }
Пример #13
0
        /// <summary>
        /// Follow any type segments on the path, stopping at the first segment that isn't a type token.
        /// </summary>
        /// <param name="firstTypeToken">the first type segment</param>
        /// <param name="model">the model these types are contained in.</param>
        /// <param name="maxDepth">the maximum recursive depth</param>
        /// <param name="resolver">Resolver for uri parser.</param>
        /// <param name="currentLevelType">the top level type, will be overwritten with the last entity type in the chain</param>
        /// <param name="firstNonTypeToken">the first non type token in the path</param>
        /// <returns>A path with type segments added to it.</returns>
        public static IEnumerable <ODataPathSegment> FollowTypeSegments(PathSegmentToken firstTypeToken, IEdmModel model, int maxDepth, ODataUriResolver resolver, ref IEdmStructuredType currentLevelType, out PathSegmentToken firstNonTypeToken)
        {
            ExceptionUtils.CheckArgumentNotNull(firstTypeToken, "firstTypeToken");
            ExceptionUtils.CheckArgumentNotNull(model, "model");

            if (!firstTypeToken.IsNamespaceOrContainerQualified())
            {
                throw new ODataException(ODataErrorStrings.SelectExpandPathBinder_FollowNonTypeSegment(firstTypeToken.Identifier));
            }

            int index = 0;
            List <ODataPathSegment> pathToReturn = new List <ODataPathSegment>();
            PathSegmentToken        currentToken = firstTypeToken;

            while (currentToken.IsNamespaceOrContainerQualified() && currentToken.NextToken != null)
            {
                IEdmType previousLevelEntityType = currentLevelType;
                currentLevelType = UriEdmHelpers.FindTypeFromModel(model, currentToken.Identifier, resolver) as IEdmStructuredType;
                if (currentLevelType == null)
                {
                    // TODO: fix this error message?
                    throw new ODataException(ODataErrorStrings.ExpandItemBinder_CannotFindType(currentToken.Identifier));
                }

                UriEdmHelpers.CheckRelatedTo(previousLevelEntityType, currentLevelType);
                pathToReturn.Add(new TypeSegment(currentLevelType, /*entitySet*/ null));

                index++;
                currentToken = currentToken.NextToken;

                if (index >= maxDepth)
                {
                    throw new ODataException(ODataErrorStrings.ExpandItemBinder_PathTooDeep);
                }
            }

            firstNonTypeToken = currentToken;

            return(pathToReturn);
        }
Пример #14
0
        /// <summary>Tries to convert values to the keys of the specified type.</summary>
        /// <param name="targetEntityType">The specified type.</param>
        /// <param name="keyPairs">The converted key-value pairs.</param>
        /// <param name="resolver">The resolver to use.</param>
        /// <returns>true if all values were converted; false otherwise.</returns>
        public bool TryConvertValues(IEdmEntityType targetEntityType, out IEnumerable <KeyValuePair <string, object> > keyPairs, ODataUriResolver resolver)
        {
            Debug.Assert(!this.IsEmpty, "!this.IsEmpty -- caller should check");

            if (this.NamedValues != null)
            {
                keyPairs = resolver.ResolveKeys(targetEntityType, this.NamedValues, this.ConvertValueWrapper);
            }
            else
            {
                Debug.Assert(this.positionalValues != null, "positionalValues != null -- otherwise this is Empty");
                keyPairs = resolver.ResolveKeys(targetEntityType, this.PositionalValues, this.ConvertValueWrapper);
            }

            return(true);
        }
Пример #15
0
        /// <summary>
        /// Tries to handle the current segment as a key property value.
        /// </summary>
        /// <param name="segmentText">The segment text.</param>
        /// <param name="previous">The previous segment.</param>
        /// <param name="previousKeySegment">The parent node's key segment.</param>
        /// <param name="odataUrlKeyDelimiter">Key delimiter used in url.</param>
        /// <param name="resolver">The resolver to use.</param>
        /// <param name="keySegment">The key segment that was created if the segment could be interpreted as a key.</param>
        /// <param name="enableUriTemplateParsing">Whether Uri template parsing is enabled.</param>
        /// <returns>Whether or not the segment was interpreted as a key.</returns>
        internal static bool TryHandleSegmentAsKey(string segmentText, ODataPathSegment previous, KeySegment previousKeySegment, ODataUrlKeyDelimiter odataUrlKeyDelimiter, ODataUriResolver resolver, out KeySegment keySegment, bool enableUriTemplateParsing = false)
        {
            Debug.Assert(previous != null, "previous != null");
            Debug.Assert(odataUrlKeyDelimiter != null, "odataUrlKeyDelimiter != null");
            Debug.Assert(resolver != null, "resolver != null");

            keySegment = null;

            // If the current convention does not support keys-as-segments, then this does not apply.
            if (!odataUrlKeyDelimiter.EnableKeyAsSegment)
            {
                return(false);
            }

            // Keys only apply to collections, so if the prior segment is already a singleton, do not treat this segment as a key.
            if (previous.SingleResult)
            {
                return(false);
            }

            // System segments (ie '$count') are never keys.
            if (IsSystemSegment(segmentText))
            {
                return(false);
            }

            // If the previous type is not an entity collection type
            // TODO: collapse this and SingleResult.
            IEdmEntityType targetEntityType;

            if (previous.TargetEdmType == null || !previous.TargetEdmType.IsEntityOrEntityCollectionType(out targetEntityType))
            {
                return(false);
            }

            // Previously KeyAsSegment only allows single key, but we can also leverage related key finder to auto fill
            // missed key value from referential constraint information, which would be done in CreateKeySegment.
            // CreateKeySegment method will check whether key properties are missing after taking in related key values.
            keySegment = CreateKeySegment(previous, previousKeySegment, SegmentArgumentParser.FromSegment(segmentText, enableUriTemplateParsing), resolver);

            return(true);
        }
Пример #16
0
        /// <summary>
        /// Constructs a new SelectBinder.
        /// </summary>
        /// <param name="model">The model used for binding.</param>
        /// <param name="edmType">The entity type that the $select is being applied to.</param>
        /// <param name="maxDepth">the maximum recursive depth.</param>
        /// <param name="expandClauseToDecorate">The already built expand clause to decorate</param>
        /// <param name="resolver">Resolver for uri parser.</param>
        public SelectBinder(IEdmModel model, IEdmStructuredType edmType, int maxDepth, SelectExpandClause expandClauseToDecorate, ODataUriResolver resolver, BindingState state)
        {
            ExceptionUtils.CheckArgumentNotNull(model, "tokenIn");
            ExceptionUtils.CheckArgumentNotNull(edmType, "entityType");
            ExceptionUtils.CheckArgumentNotNull(resolver, "resolver");

            this.visitor = new SelectPropertyVisitor(model, edmType, maxDepth, expandClauseToDecorate, resolver, state);
        }
Пример #17
0
 /// <summary>
 /// Wraps a call to IEdmModel.FindType.
 /// </summary>
 /// <param name="model">The model to search.</param>
 /// <param name="qualifiedName">The qualified name of the type to find within the model.</param>
 /// <param name="resolver">Resolver for this func.</param>
 /// <returns>The requested type, or null if no such type exists.</returns>
 public static IEdmSchemaType FindTypeFromModel(IEdmModel model, string qualifiedName, ODataUriResolver resolver)
 {
     return(resolver.ResolveType(model, qualifiedName));
 }
Пример #18
0
        /// <summary>
        /// Given a property name, if the associated type reference is structured, then this returns
        /// the property of the structured type. Otherwise, it returns null.
        /// </summary>
        /// <param name="parentReference">The parent type to be used to find binding options.</param>
        /// <param name="propertyName">The string designated the property name to be bound.</param>
        /// <param name="resolver">Resolver for uri parser.</param>
        /// <returns>The property associated with string and parent type.</returns>
        internal static IEdmProperty BindProperty(IEdmTypeReference parentReference, string propertyName, ODataUriResolver resolver)
        {
            ExceptionUtils.CheckArgumentNotNull(resolver, "resolver");

            IEdmStructuredTypeReference structuredParentType =
                parentReference == null ? null : parentReference.AsStructuredOrNull();

            return(structuredParentType == null ? null : resolver.ResolveProperty(structuredParentType.StructuredDefinition(), propertyName));
        }
Пример #19
0
        /// <summary>
        /// Wraps call to FindTypeFromModel for an Enum type.
        /// </summary>
        /// <param name="model">the model to search</param>
        /// <param name="qualifiedName">the name to find within the model</param>
        /// <returns>a type reference to the enum type, or null if no such type exists.</returns>
        public static IEdmEnumType FindEnumTypeFromModel(IEdmModel model, string qualifiedName)
        {
            IEdmEnumType enumType = FindTypeFromModel(model, qualifiedName, ODataUriResolver.GetUriResolver(null)) as IEdmEnumType;

            return(enumType);
        }
Пример #20
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);
        }
        /// <summary>
        /// Build a segment from a token.
        /// </summary>
        /// <param name="tokenIn">the token to bind</param>
        /// <param name="model">The model.</param>
        /// <param name="edmType">the type of the current scope based on type segments.</param>
        /// <param name="resolver">Resolver for uri parser.</param>
        /// <returns>The segment created from the token.</returns>
        public static ODataPathSegment ConvertNonTypeTokenToSegment(PathSegmentToken tokenIn, IEdmModel model, IEdmStructuredType edmType, ODataUriResolver resolver)
        {
            ExceptionUtils.CheckArgumentNotNull(resolver, "resolver");

            ODataPathSegment nextSegment;

            if (TryBindAsDeclaredProperty(tokenIn, edmType, resolver, out nextSegment))
            {
                return(nextSegment);
            }

            // Operations must be container-qualified, and because the token type indicates it was not a .-seperated identifier, we should not try to look up operations.
            if (tokenIn.IsNamespaceOrContainerQualified())
            {
                if (TryBindAsOperation(tokenIn, model, edmType, out nextSegment))
                {
                    return(nextSegment);
                }

                // If an action or function is requested in a selectItem using a qualifiedActionName or a qualifiedFunctionName
                // and that operation cannot be bound to the entities requested, the service MUST ignore the selectItem.
                if (!edmType.IsOpen)
                {
                    return(null);
                }
            }

            if (edmType.IsOpen)
            {
                return(new DynamicPathSegment(tokenIn.Identifier));
            }

            throw ExceptionUtil.CreatePropertyNotFoundException(tokenIn.Identifier, edmType.FullTypeName());
        }
Пример #22
0
        /// <summary>
        /// Tries to bind a given token as an a declared structural or navigation property.
        /// </summary>
        /// <param name="tokenIn">Token to bind.</param>
        /// <param name="edmType">the type to search for this property</param>
        /// <param name="resolver">Resolver for uri parser.</param>
        /// <param name="segment">Bound segment if the token was bound to a declared property successfully, or null.</param>
        /// <returns>True if the token was bound successfully, or false otherwise.</returns>
        private static bool TryBindAsDeclaredProperty(PathSegmentToken tokenIn, IEdmStructuredType edmType, ODataUriResolver resolver, out ODataPathSegment segment)
        {
            IEdmProperty prop = resolver.ResolveProperty(edmType, tokenIn.Identifier);

            if (prop == null)
            {
                segment = null;
                return(false);
            }

            if (prop.PropertyKind == EdmPropertyKind.Structural)
            {
                segment = new PropertySegment((IEdmStructuralProperty)prop);
                return(true);
            }

            if (prop.PropertyKind == EdmPropertyKind.Navigation)
            {
                segment = new NavigationPropertySegment((IEdmNavigationProperty)prop, null /*TODO: set*/);
                return(true);
            }

            throw new ODataException(ODataErrorStrings.SelectExpandBinder_UnknownPropertyType(prop.Name));
        }
Пример #23
0
 /// <summary>
 /// Build a property visitor to visit the select tree and decorate a SelectExpandClause
 /// </summary>
 /// <param name="model">The model used for binding.</param>
 /// <param name="edmType">The entity type that the $select is being applied to.</param>
 /// <param name="maxDepth">the maximum recursive depth.</param>
 /// <param name="expandClauseToDecorate">The already built expand clause to decorate</param>
 /// <param name="resolver">Resolver for uri parser.</param>
 /// <param name="state">The binding state of the visitor.</param>
 public SelectPropertyVisitor(IEdmModel model, IEdmStructuredType edmType, int maxDepth, SelectExpandClause expandClauseToDecorate, ODataUriResolver resolver, BindingState state)
 {
     this.model    = model;
     this.edmType  = edmType;
     this.maxDepth = maxDepth;
     this.expandClauseToDecorate = expandClauseToDecorate;
     this.resolver = resolver;
     this.state    = state;
 }
Пример #24
0
        /// <summary>
        /// Build a segment from a token.
        /// </summary>
        /// <param name="tokenIn">the token to bind</param>
        /// <param name="model">The model.</param>
        /// <param name="edmType">the type of the current scope based on type segments.</param>
        /// <param name="resolver">Resolver for uri parser.</param>
        /// <param name="state">The binding state.</param>
        /// <returns>The segment created from the token.</returns>
        public static ODataPathSegment ConvertNonTypeTokenToSegment(PathSegmentToken tokenIn, IEdmModel model, IEdmStructuredType edmType, ODataUriResolver resolver, BindingState state = null)
        {
            ExceptionUtils.CheckArgumentNotNull(resolver, "resolver");

            ODataPathSegment nextSegment;

            if (UriParserHelper.IsAnnotation(tokenIn.Identifier))
            {
                if (TryBindAsDeclaredTerm(tokenIn, model, resolver, out nextSegment))
                {
                    return(nextSegment);
                }

                string qualifiedTermName = tokenIn.Identifier.Remove(0, 1);
                int    separator         = qualifiedTermName.LastIndexOf(".", StringComparison.Ordinal);
                string namespaceName     = qualifiedTermName.Substring(0, separator);
                string termName          = qualifiedTermName.Substring(separator == 0 ? 0 : separator + 1);

                // Don't allow selecting odata control information
                if (String.Compare(namespaceName, ODataConstants.ODataPrefix, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(tokenIn.Identifier));
                }

                return(new AnnotationSegment(new EdmTerm(namespaceName, termName, EdmCoreModel.Instance.GetUntyped())));
            }

            EndPathToken endPathToken = new EndPathToken(tokenIn.Identifier, null);

            if ((state?.IsCollapsed ?? false) && !(state?.AggregatedPropertyNames?.Contains(endPathToken) ?? false))
            {
                throw new ODataException(ODataErrorStrings.ApplyBinder_GroupByPropertyNotPropertyAccessValue(tokenIn.Identifier));
            }

            if (TryBindAsDeclaredProperty(tokenIn, edmType, resolver, out nextSegment))
            {
                return(nextSegment);
            }

            // Operations must be container-qualified, and because the token type indicates it was not a .-separated identifier, we should not try to look up operations.
            if (tokenIn.IsNamespaceOrContainerQualified())
            {
                if (TryBindAsOperation(tokenIn, model, edmType, out nextSegment))
                {
                    return(nextSegment);
                }

                // If an action or function is requested in a selectItem using a qualifiedActionName or a qualifiedFunctionName
                // and that operation cannot be bound to the entities requested, the service MUST ignore the selectItem.
                if (!edmType.IsOpen)
                {
                    return(null);
                }
            }

            if (edmType.IsOpen || (state?.AggregatedPropertyNames?.Contains(endPathToken) ?? false))
            {
                return(new DynamicPathSegment(tokenIn.Identifier));
            }

            throw new ODataException(ODataErrorStrings.MetadataBinder_PropertyNotDeclared(edmType.FullTypeName(), tokenIn.Identifier));
        }
Пример #25
0
 /// <summary>
 /// Try to bind a dotted identifier as enum node
 /// </summary>
 /// <param name="dottedIdentifierToken">a dotted identifier token</param>
 /// <param name="parent">the parent node</param>
 /// <param name="state">the current state of the binding algorithm</param>
 /// <param name="resolver">ODataUriResolver</param>
 /// <param name="boundEnum">the output bound enum node</param>
 /// <returns>true if we bound an enum node, false otherwise.</returns>
 internal static bool TryBindDottedIdentifierAsEnum(DottedIdentifierToken dottedIdentifierToken, SingleValueNode parent, BindingState state, ODataUriResolver resolver, out QueryNode boundEnum)
 {
     return(TryBindIdentifier(dottedIdentifierToken.Identifier, null, state.Model, resolver, out boundEnum));
 }
Пример #26
0
        /// <summary>
        /// Try to get an IEdmTypeReference for a given type as a string, returns null if none exists
        /// </summary>
        /// <param name="model">the model for validation</param>
        /// <param name="fullTypeName">the type name to find</param>
        /// <param name="resolver">Resolver for this func.</param>
        /// <returns>an IEdmTypeReference for this type string.</returns>
        private static IEdmTypeReference TryGetTypeReference(IEdmModel model, string fullTypeName, ODataUriResolver resolver)
        {
            IEdmTypeReference typeReference = UriEdmHelpers.FindTypeFromModel(model, fullTypeName, resolver).ToTypeReference();

            if (typeReference == null)
            {
                if (fullTypeName.StartsWith("Collection", StringComparison.Ordinal))
                {
                    string[] tokenizedString = fullTypeName.Split('(');
                    string   baseElementType = tokenizedString[1].Split(')')[0];
                    return(EdmCoreModel.GetCollection(UriEdmHelpers.FindTypeFromModel(model, baseElementType, resolver).ToTypeReference()));
                }
                else
                {
                    return(null);
                }
            }

            return(typeReference);
        }
Пример #27
0
 /// <summary>
 /// Constructs a BinaryOperatorBinder with the given method to be used binding the parent token if needed.
 /// </summary>
 /// <param name="bindMethod">Method to use for binding the parent token, if needed.</param>
 /// <param name="resolver">Resolver for parsing.</param>
 internal BinaryOperatorBinder(Func <QueryToken, QueryNode> bindMethod, ODataUriResolver resolver)
 {
     this.bindMethod = bindMethod;
     this.resolver   = resolver;
 }