/// <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());
        }
예제 #2
0
        /// <summary>
        /// Process a <see cref="SelectTermToken"/> to identify whether it's a Wildcard path.
        /// </summary>
        /// <param name="selectToken">the select token to process.</param>
        /// <param name="newSelectItem">the built select item to out.</param>
        private bool ProcessWildcardTokenPath(SelectTermToken selectToken, out SelectItem newSelectItem)
        {
            newSelectItem = null;
            if (selectToken == null || selectToken.PathToProperty == null)
            {
                return(false);
            }

            PathSegmentToken pathToken = selectToken.PathToProperty;

            if (SelectPathSegmentTokenBinder.TryBindAsWildcard(pathToken, this.Model, out newSelectItem))
            {
                // * or Namespace.*
                if (pathToken.NextToken != null)
                {
                    throw new ODataException(ODataErrorStrings.SelectExpandBinder_InvalidIdentifierAfterWildcard(pathToken.NextToken.Identifier));
                }

                VerifyNoQueryOptionsNested(selectToken, pathToken.Identifier);

                return(true);
            }

            return(false);
        }
예제 #3
0
        /// <summary>
        /// Parse the expand option in the select/expand option text.
        /// </summary>
        /// <param name="pathToken">The path segment token</param>
        /// <returns>The expand option for select/expand</returns>
        private ExpandToken ParseInnerExpand(PathSegmentToken pathToken)
        {
            // advance to the equal sign
            this.lexer.NextToken();

            string expandText = this.ReadQueryOption();

            IEdmStructuredType targetStructuredType = null;

            if (this.resolver != null && this.parentStructuredType != null)
            {
                var parentProperty = this.resolver.ResolveProperty(parentStructuredType, pathToken.Identifier);

                // it is a property, need to find the type.
                // Like $expand=Friends($expand=Trips($expand=*)), when expandText becomes "Trips($expand=*)",
                // find navigation property Trips of Friends, then get Entity type of Trips.
                // or for select query like: $select=Address($expand=City)
                if (parentProperty != null)
                {
                    targetStructuredType = parentProperty.Type.ToStructuredType();
                }
            }

            SelectExpandParser innerExpandParser = new SelectExpandParser(
                resolver,
                expandText,
                targetStructuredType,
                this.maxRecursionDepth - 1,
                this.enableCaseInsensitiveBuiltinIdentifier,
                this.enableNoDollarQueryOptions);

            return(innerExpandParser.ParseExpand());
        }
예제 #4
0
        /// <summary>
        /// Build a wildcard selection item
        /// </summary>
        /// <param name="tokenIn">the token to bind to a wildcard</param>
        /// <param name="model">the model to search for this wildcard</param>
        /// <param name="item">the new wildcard selection item, if we found one</param>
        /// <returns>true if we successfully bound to a wildcard, false otherwise</returns>
        public static bool TryBindAsWildcard(PathSegmentToken tokenIn, IEdmModel model, out SelectItem item)
        {
            bool isTypeToken = tokenIn.IsNamespaceOrContainerQualified();
            bool wildcard    = tokenIn.Identifier.EndsWith("*", StringComparison.Ordinal);

            if (isTypeToken && wildcard)
            {
                string namespaceName = tokenIn.Identifier.Substring(0, tokenIn.Identifier.Length - 2);

                if (model.DeclaredNamespaces.Any(declaredNamespace => declaredNamespace.Equals(namespaceName, StringComparison.Ordinal)))
                {
                    item = new NamespaceQualifiedWildcardSelectItem(namespaceName);
                    return(true);
                }
            }

            if (tokenIn.Identifier == "*")
            {
                item = new WildcardSelectItem();
                return(true);
            }

            item = null;
            return(false);
        }
예제 #5
0
        /// <summary>
        /// Invert the all of the paths in an expandToken, such that they are now in the same order as they are present in the
        /// base url
        /// </summary>
        /// <param name="treeToInvert">the tree to invert paths on</param>
        /// <returns>a new tree with all of its paths inverted</returns>
        public ExpandToken NormalizePaths(ExpandToken treeToInvert)
        {
            // iterate through each expand term token, and reverse the tree in its path property
            List <ExpandTermToken> updatedTerms = new List <ExpandTermToken>();

            foreach (ExpandTermToken term in treeToInvert.ExpandTerms)
            {
                PathReverser     pathReverser = new PathReverser();
                PathSegmentToken reversedPath = term.PathToNavigationProp.Accept(pathReverser);

                // we also need to call the select token normalizer for this level to reverse the select paths
                SelectToken newSelectToken = term.SelectOption;
                if (term.SelectOption != null)
                {
                    newSelectToken = SelectTreeNormalizer.NormalizeSelectTree(term.SelectOption);
                }

                ExpandToken subExpandTree;
                if (term.ExpandOption != null)
                {
                    subExpandTree = this.NormalizePaths(term.ExpandOption);
                }
                else
                {
                    subExpandTree = null;
                }

                ExpandTermToken newTerm = new ExpandTermToken(reversedPath, term.FilterOption, term.OrderByOptions, term.TopOption, term.SkipOption, term.CountQueryOption, term.LevelsOption, term.SearchOption, newSelectToken, subExpandTree, term.ComputeOption);
                updatedTerms.Add(newTerm);
            }

            return(new ExpandToken(updatedTerms));
        }
예제 #6
0
        /// <summary>
        /// Parses a single term in a comma separated list of things to expand.
        /// </summary>
        /// <returns>A token list representing thing to expand, the expand option star will have more than one items in the list.</returns>
        private List <ExpandTermToken> ParseSingleExpandTerm()
        {
            this.isSelect = false;

            var termParser             = new SelectExpandTermParser(this.lexer, this.MaxPathDepth, this.isSelect);
            PathSegmentToken pathToken = termParser.ParseTerm(allowRef: true);

            string optionsText = null;

            if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen)
            {
                optionsText = this.lexer.AdvanceThroughBalancedParentheticalExpression();

                // Move lexer to what is after the parenthesis expression. Now CurrentToken will be the next thing.
                this.lexer.NextToken();
            }

            if (expandOptionParser == null)
            {
                expandOptionParser = new ExpandOptionParser(
                    this.resolver,
                    this.parentEntityType,
                    this.maxRecursiveDepth,
                    this.enableCaseInsensitiveBuiltinIdentifier,
                    this.enableNoDollarQueryOptions)
                {
                    MaxFilterDepth  = MaxFilterDepth,
                    MaxOrderByDepth = MaxOrderByDepth,
                    MaxSearchDepth  = MaxSearchDepth
                };
            }

            return(this.expandOptionParser.BuildExpandTermToken(pathToken, optionsText));
        }
예제 #7
0
        /// <summary>
        /// Parse the select option in the select/expand option text.
        /// </summary>
        /// <param name="pathToken">The path segment token</param>
        /// <returns>The select option for select/expand</returns>
        private SelectToken ParseInnerSelect(PathSegmentToken pathToken)
        {
            // advance to the equal sign
            this.lexer.NextToken();
            string selectText = this.ReadQueryOption();

            IEdmStructuredType targetStructuredType = null;

            if (this.resolver != null && this.parentStructuredType != null)
            {
                var parentProperty = this.resolver.ResolveProperty(parentStructuredType, pathToken.Identifier);

                // It is a property, need to find the type.
                // or for select query like: $select=Address($expand=City)
                if (parentProperty != null)
                {
                    targetStructuredType = parentProperty.Type.ToStructuredType();
                }
            }

            SelectExpandParser innerSelectParser = new SelectExpandParser(
                resolver,
                selectText,
                targetStructuredType,
                this.maxRecursionDepth - 1,
                this.enableCaseInsensitiveBuiltinIdentifier,
                this.enableNoDollarQueryOptions);

            return(innerSelectParser.ParseSelect());
        }
        /// <summary>
        /// Uses the ExpressionLexer to visit the next ExpressionToken, and delegates parsing of segments, type segments, identifiers,
        /// and the star token to other methods.
        /// </summary>
        /// <param name="previousSegment">Previously parsed PathSegmentToken, or null if this is the first token.</param>
        /// <param name="allowRef">Whether the $ref operation is valid in this token.</param>
        /// <returns>A parsed PathSegmentToken representing the next segment in this path.</returns>
        private PathSegmentToken ParseSegment(PathSegmentToken previousSegment, bool allowRef)
        {
            // TODO $count is defined in specification for expand, it is not supported now. Also note $count is not supported with star as expand option.
            if (this.lexer.CurrentToken.Text.StartsWith("$", StringComparison.Ordinal) &&
                (!allowRef || this.lexer.CurrentToken.Text != UriQueryConstants.RefSegment) &&
                this.lexer.CurrentToken.Text != UriQueryConstants.CountSegment
                )
            {
                throw new ODataException(ODataErrorStrings.UriSelectParser_SystemTokenInSelectExpand(this.lexer.CurrentToken.Text, this.lexer.ExpressionText));
            }

            // Some check here to throw exception, both prop1/*/prop2 and */$ref/prop will throw exception, both are for $expand cases
            if (!isSelect)
            {
                if (previousSegment != null && previousSegment.Identifier == UriQueryConstants.Star && this.lexer.CurrentToken.GetIdentifier() != UriQueryConstants.RefSegment)
                {
                    // Star can only be followed with $ref
                    throw new ODataException(ODataErrorStrings.ExpressionToken_OnlyRefAllowWithStarInExpand);
                }
                else if (previousSegment != null && previousSegment.Identifier == UriQueryConstants.RefSegment)
                {
                    // $ref should not have more property followed.
                    throw new ODataException(ODataErrorStrings.ExpressionToken_NoPropAllowedAfterRef);
                }
            }

            string propertyName;

            if (this.lexer.PeekNextToken().Kind == ExpressionTokenKind.Dot)
            {
                propertyName = this.lexer.ReadDottedIdentifier(this.isSelect);
            }
            else if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.Star)
            {
                // "*/$ref" is supported in expand
                if (this.lexer.PeekNextToken().Kind == ExpressionTokenKind.Slash && isSelect)
                {
                    throw new ODataException(ODataErrorStrings.ExpressionToken_IdentifierExpected(this.lexer.Position));
                }
                else if (previousSegment != null && !isSelect)
                {
                    // expand option like "customer?$expand=VIPCustomer/*" is not allowed as specification does not allowed any property before *.
                    throw new ODataException(ODataErrorStrings.ExpressionToken_NoSegmentAllowedBeforeStarInExpand);
                }

                propertyName = this.lexer.CurrentToken.Text;
                this.lexer.NextToken();
            }
            else
            {
                propertyName = this.lexer.CurrentToken.GetIdentifier();
                this.lexer.NextToken();
            }

            return(new NonSystemToken(propertyName, null, previousSegment));
        }
        /// <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 (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())));
            }

            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 new ODataException(ODataErrorStrings.MetadataBinder_PropertyNotDeclared(edmType.FullTypeName(), tokenIn.Identifier));
        }
예제 #10
0
        private static void VerifySelectedPath(SelectTermToken selectedToken)
        {
            PathSegmentToken current = selectedToken.PathToProperty;

            while (current != null)
            {
                if (current is SystemToken)
                {
                    // It's not allowed to set a system token in a select clause.
                    throw new ODataException(ODataErrorStrings.SelectExpandBinder_SystemTokenInSelect(current.Identifier));
                }

                current = current.NextToken;
            }
        }
예제 #11
0
        /// <summary>
        /// Parses a single term in a comma separated list of things to expand.
        /// </summary>
        /// <returns>A token list representing thing to expand, the expand option star will have more than one items in the list.</returns>
        private List <ExpandTermToken> ParseSingleExpandTerm()
        {
            this.isSelect = false;

            var termParser             = new SelectExpandTermParser(this.lexer, this.MaxPathDepth, this.isSelect);
            PathSegmentToken pathToken = termParser.ParseTerm(allowRef: true);

            string optionsText = null;

            if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen)
            {
                optionsText = this.lexer.AdvanceThroughBalancedParentheticalExpression();

                // Move lexer to what is after the parenthesis expression. Now CurrentToken will be the next thing.
                this.lexer.NextToken();
            }

            return(this.SelectExpandOptionParser.BuildExpandTermToken(pathToken, optionsText));
        }
예제 #12
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);
        }
예제 #13
0
        /// <summary>
        /// Get the path string for a path segment token.
        /// </summary>
        /// <param name="head">The head of the path</param>
        /// <returns>The path string.</returns>
        internal static string ToPathString(PathSegmentToken head)
        {
            StringBuilder    sb   = new StringBuilder();
            PathSegmentToken curr = head;

            while (curr != null)
            {
                sb.Append(curr.Identifier);

                NonSystemToken nonSystem = curr as NonSystemToken;
                if (nonSystem != null && nonSystem.NamedValues != null)
                {
                    sb.Append("(");
                    bool first = true;
                    foreach (var item in nonSystem.NamedValues)
                    {
                        if (first)
                        {
                            first = false;
                        }
                        else
                        {
                            sb.Append(",");
                        }

                        sb.Append(item.Name).Append("=").Append(item.Value.Value);
                    }

                    sb.Append(")");
                }

                curr = curr.NextToken;
                if (curr != null)
                {
                    sb.Append("/");
                }
            }

            return(sb.ToString());
        }
예제 #14
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);
        }
예제 #15
0
        /// <summary>
        /// Parses a select or expand term into a PathSegmentToken.
        /// Assumes the lexer is positioned at the beginning of the term to parse.
        /// When done, the lexer will be positioned at whatever is after the identifier.
        /// </summary>
        /// <param name="allowRef">Whether the $ref operation is valid in this token.</param>
        /// <returns>parsed query token</returns>
        internal PathSegmentToken ParseTerm(bool allowRef = false)
        {
            int pathLength;
            PathSegmentToken token = this.ParseSegment(null, allowRef);

            if (token != null)
            {
                pathLength = 1;
            }
            else
            {
                return(null);
            }

            this.CheckPathLength(pathLength);

            // If this property was a path, walk that path. e.g. SomeComplex/SomeInnerComplex/SomeNavProp
            while (this.lexer.CurrentToken.Kind == ExpressionTokenKind.Slash)
            {
                // Move from '/' to the next segment
                this.lexer.NextToken();

                // TODO: Could remove V4 if we don't want to allow a trailing '/' character
                // Allow a single trailing slash for backwards compatibility with the WCF DS Server parser.
                if (pathLength > 1 && this.lexer.CurrentToken.Kind == ExpressionTokenKind.End)
                {
                    break;
                }

                token = this.ParseSegment(token, allowRef);
                if (token != null)
                {
                    this.CheckPathLength(++pathLength);
                }
            }

            return(token);
        }
예제 #16
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));
        }
예제 #17
0
        /// <summary>
        /// Building off a PathSegmentToken, continue parsing any select options (nested $filter, $expand, etc)
        /// to build up an SelectTermToken which fully represents the tree that makes up this select term.
        /// </summary>
        /// <param name="pathToken">The PathSegmentToken representing the parsed select path whose options we are now parsing.</param>
        /// <param name="optionsText">A string of the text between the parenthesis after a select option.</param>
        /// <returns>The select term token based on the path token, and all available select options.</returns>
        internal SelectTermToken BuildSelectTermToken(PathSegmentToken pathToken, string optionsText)
        {
            // Setup a new lexer for parsing the optionsText
            this.lexer = new ExpressionLexer(optionsText ?? "", true /*moveToFirstToken*/, true /*useSemicolonDelimiter*/);

            QueryToken filterOption = null;
            IEnumerable <OrderByToken> orderByOptions = null;
            long?        topOption     = null;
            long?        skipOption    = null;
            bool?        countOption   = null;
            QueryToken   searchOption  = null;
            SelectToken  selectOption  = null;
            ComputeToken computeOption = null;

            if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen)
            {
                // advance past the '('
                this.lexer.NextToken();

                // Check for (), which is not allowed.
                if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen)
                {
                    throw new ODataException(ODataErrorStrings.UriParser_MissingSelectOption(pathToken.Identifier));
                }

                // Look for all the supported query options
                while (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen)
                {
                    string text = this.enableCaseInsensitiveBuiltinIdentifier
                        ? this.lexer.CurrentToken.Text.ToLowerInvariant()
                        : this.lexer.CurrentToken.Text;

                    // Prepend '$' prefix if needed.
                    if (this.enableNoDollarQueryOptions && !text.StartsWith(UriQueryConstants.DollarSign, StringComparison.Ordinal))
                    {
                        text = string.Format(CultureInfo.InvariantCulture, "{0}{1}", UriQueryConstants.DollarSign, text);
                    }

                    switch (text)
                    {
                    case ExpressionConstants.QueryOptionFilter:     // inner $filter
                        filterOption = ParseInnerFilter();
                        break;

                    case ExpressionConstants.QueryOptionOrderby:     // inner $orderby
                        orderByOptions = ParseInnerOrderBy();
                        break;

                    case ExpressionConstants.QueryOptionTop:     // inner $top
                        topOption = ParseInnerTop();
                        break;

                    case ExpressionConstants.QueryOptionSkip:     // innner $skip
                        skipOption = ParseInnerSkip();
                        break;

                    case ExpressionConstants.QueryOptionCount:     // inner $count
                        countOption = ParseInnerCount();
                        break;

                    case ExpressionConstants.QueryOptionSearch:     // inner $search
                        searchOption = ParseInnerSearch();
                        break;

                    case ExpressionConstants.QueryOptionSelect:     // inner $select
                        selectOption = ParseInnerSelect(pathToken);
                        break;

                    case ExpressionConstants.QueryOptionCompute:     // inner $compute
                        computeOption = ParseInnerCompute();
                        break;

                    default:
                        throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText));
                    }
                }

                // Move past the ')'
                this.lexer.NextToken();
            }

            // Either there was no '(' at all or we just read past the ')' so we should be at the end
            if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End)
            {
                throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText));
            }

            return(new SelectTermToken(pathToken, filterOption, orderByOptions, topOption, skipOption, countOption, searchOption, selectOption, computeOption));
        }
예제 #18
0
        /// <summary>
        /// Building off of a PathSegmentToken, continue parsing any expand options (nested $filter, $expand, etc)
        /// to build up an ExpandTermToken which fully represents the tree that makes up this expand term.
        /// </summary>
        /// <param name="pathToken">The PathSegmentToken representing the parsed expand path whose options we are now parsing.</param>
        /// <param name="optionsText">A string of the text between the parenthesis after an expand option.</param>
        /// <returns>The list of expand term tokens based on the path token, and all available expand options.</returns>
        internal List <ExpandTermToken> BuildExpandTermToken(PathSegmentToken pathToken, string optionsText)
        {
            // Setup a new lexer for parsing the optionsText
            this.lexer = new ExpressionLexer(optionsText ?? "", true /*moveToFirstToken*/, true /*useSemicolonDelimiter*/);

            // $expand option with star only support $ref option, $expand option property could be "*" or "*/$ref", special logic will be adopted.
            if (pathToken.Identifier == UriQueryConstants.Star || (pathToken.Identifier == UriQueryConstants.RefSegment && pathToken.NextToken.Identifier == UriQueryConstants.Star))
            {
                return(BuildStarExpandTermToken(pathToken));
            }

            QueryToken filterOption = null;
            IEnumerable <OrderByToken> orderByOptions = null;
            long?       topOption    = null;
            long?       skipOption   = null;
            bool?       countOption  = null;
            long?       levelsOption = null;
            QueryToken  searchOption = null;
            SelectToken selectOption = null;
            ExpandToken expandOption = null;

            if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen)
            {
                // advance past the '('
                this.lexer.NextToken();

                // Check for (), which is not allowed.
                if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen)
                {
                    throw new ODataException(ODataErrorStrings.UriParser_MissingExpandOption(pathToken.Identifier));
                }

                // Look for all the supported query options
                while (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen)
                {
                    string text = this.enableCaseInsensitiveBuiltinIdentifier
                        ? this.lexer.CurrentToken.Text.ToLowerInvariant()
                        : this.lexer.CurrentToken.Text;

                    // Prepend '$' prefix if needed.
                    if (this.enableNoDollarQueryOptions && !text.StartsWith(UriQueryConstants.DollarSign, StringComparison.Ordinal))
                    {
                        text = string.Format(CultureInfo.InvariantCulture, "{0}{1}", UriQueryConstants.DollarSign, text);
                    }

                    switch (text)
                    {
                    case ExpressionConstants.QueryOptionFilter:
                    {
                        // advance to the equal sign
                        this.lexer.NextToken();
                        string filterText = this.ReadQueryOption();

                        UriQueryExpressionParser filterParser = new UriQueryExpressionParser(this.MaxFilterDepth, enableCaseInsensitiveBuiltinIdentifier);
                        filterOption = filterParser.ParseFilter(filterText);
                        break;
                    }

                    case ExpressionConstants.QueryOptionOrderby:
                    {
                        // advance to the equal sign
                        this.lexer.NextToken();
                        string orderByText = this.ReadQueryOption();

                        UriQueryExpressionParser orderbyParser = new UriQueryExpressionParser(this.MaxOrderByDepth, enableCaseInsensitiveBuiltinIdentifier);
                        orderByOptions = orderbyParser.ParseOrderBy(orderByText);
                        break;
                    }

                    case ExpressionConstants.QueryOptionTop:
                    {
                        // advance to the equal sign
                        this.lexer.NextToken();
                        string topText = this.ReadQueryOption();

                        // TryParse requires a non-nullable non-negative long.
                        long top;
                        if (!long.TryParse(topText, out top) || top < 0)
                        {
                            throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidTopOption(topText));
                        }

                        topOption = top;
                        break;
                    }

                    case ExpressionConstants.QueryOptionSkip:
                    {
                        // advance to the equal sign
                        this.lexer.NextToken();
                        string skipText = this.ReadQueryOption();

                        // TryParse requires a non-nullable non-negative long.
                        long skip;
                        if (!long.TryParse(skipText, out skip) || skip < 0)
                        {
                            throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidSkipOption(skipText));
                        }

                        skipOption = skip;
                        break;
                    }

                    case ExpressionConstants.QueryOptionCount:
                    {
                        // advance to the equal sign
                        this.lexer.NextToken();
                        string countText = this.ReadQueryOption();
                        switch (countText)
                        {
                        case ExpressionConstants.KeywordTrue:
                        {
                            countOption = true;
                            break;
                        }

                        case ExpressionConstants.KeywordFalse:
                        {
                            countOption = false;
                            break;
                        }

                        default:
                        {
                            throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidCountOption(countText));
                        }
                        }

                        break;
                    }

                    case ExpressionConstants.QueryOptionLevels:
                    {
                        levelsOption = ResolveLevelOption();
                        break;
                    }

                    case ExpressionConstants.QueryOptionSearch:
                    {
                        // advance to the equal sign
                        this.lexer.NextToken();
                        string searchText = this.ReadQueryOption();

                        SearchParser searchParser = new SearchParser(this.MaxSearchDepth);
                        searchOption = searchParser.ParseSearch(searchText);

                        break;
                    }

                    case ExpressionConstants.QueryOptionSelect:
                    {
                        // advance to the equal sign
                        this.lexer.NextToken();
                        string selectText = this.ReadQueryOption();

                        SelectExpandParser innerSelectParser = new SelectExpandParser(selectText, this.maxRecursionDepth, enableCaseInsensitiveBuiltinIdentifier);
                        selectOption = innerSelectParser.ParseSelect();
                        break;
                    }

                    case ExpressionConstants.QueryOptionExpand:
                    {
                        // advance to the equal sign
                        this.lexer.NextToken();
                        string expandText = this.ReadQueryOption();

                        // As 2016/1/8, the navigation property is only supported in entity type, and will support in ComplexType in future.
                        IEdmStructuredType targetEntityType = null;
                        if (this.resolver != null && this.parentEntityType != null)
                        {
                            var parentProperty = this.resolver.ResolveProperty(parentEntityType, pathToken.Identifier) as IEdmNavigationProperty;

                            // it is a navigation property, need to find the type.
                            // Like $expand=Friends($expand=Trips($expand=*)), when expandText becomes "Trips($expand=*)",
                            // find navigation property Trips of Friends, then get Entity type of Trips.
                            if (parentProperty != null)
                            {
                                targetEntityType = parentProperty.ToEntityType();
                            }
                        }

                        SelectExpandParser innerExpandParser = new SelectExpandParser(
                            resolver,
                            expandText,
                            targetEntityType,
                            this.maxRecursionDepth - 1,
                            this.enableCaseInsensitiveBuiltinIdentifier,
                            this.enableNoDollarQueryOptions);
                        expandOption = innerExpandParser.ParseExpand();
                        break;
                    }

                    default:
                    {
                        throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText));
                    }
                    }
                }

                // Move past the ')'
                this.lexer.NextToken();
            }

            // Either there was no '(' at all or we just read past the ')' so we should be at the end
            if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End)
            {
                throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText));
            }

            // TODO, there should be some check here in case pathToken identifier is $ref, select, expand and levels options are not allowed.
            List <ExpandTermToken> expandTermTokenList = new List <ExpandTermToken>();
            ExpandTermToken        currentToken        = new ExpandTermToken(pathToken, filterOption, orderByOptions, topOption,
                                                                             skipOption, countOption, levelsOption, searchOption, selectOption, expandOption);

            expandTermTokenList.Add(currentToken);

            return(expandTermTokenList);
        }
예제 #19
0
        /// <summary>
        /// Tries to bind a given token as an Operation.
        /// </summary>
        /// <param name="pathToken">Token to bind.</param>
        /// <param name="model">The model.</param>
        /// <param name="entityType">the current entity type to use as the binding type when looking for operations.</param>
        /// <param name="segment">Bound segment if the token was bound to an operation successfully, or null.</param>
        /// <returns>True if the token was bound successfully, or false otherwise.</returns>
        internal static bool TryBindAsOperation(PathSegmentToken pathToken, IEdmModel model, IEdmStructuredType entityType, out ODataPathSegment segment)
        {
            Debug.Assert(pathToken != null, "pathToken != null");
            Debug.Assert(entityType != null, "bindingType != null");

            IEnumerable <IEdmOperation> possibleFunctions = Enumerable.Empty <IEdmOperation>();
            IList <string> parameterNames = new List <string>();

            // Catch all catchable exceptions as FindDeclaredBoundOperations is implemented by anyone.
            // If an exception occurs it will be suppressed and the possible functions will be empty and return false.
            try
            {
                int wildCardPos = pathToken.Identifier.IndexOf("*", StringComparison.Ordinal);
                if (wildCardPos > -1)
                {
                    string namespaceName = pathToken.Identifier.Substring(0, wildCardPos - 1);
                    possibleFunctions = model.FindBoundOperations(entityType).Where(o => o.Namespace == namespaceName);
                }
                else
                {
                    NonSystemToken nonSystemToken = pathToken as NonSystemToken;
                    if (nonSystemToken != null && nonSystemToken.NamedValues != null)
                    {
                        parameterNames = nonSystemToken.NamedValues.Select(s => s.Name).ToList();
                    }

                    if (parameterNames.Count > 0)
                    {
                        // Always force to use fully qualified name when select operation
                        possibleFunctions = model.FindBoundOperations(entityType).FilterByName(true, pathToken.Identifier).FilterOperationsByParameterNames(parameterNames, false);
                    }
                    else
                    {
                        possibleFunctions = model.FindBoundOperations(entityType).FilterByName(true, pathToken.Identifier);
                    }
                }
            }
            catch (Exception exc)
            {
                if (!ExceptionUtils.IsCatchableExceptionType(exc))
                {
                    throw;
                }
            }

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

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

            if (!possibleFunctions.HasAny())
            {
                segment = null;
                return(false);
            }

            possibleFunctions.EnsureOperationsBoundWithBindingParameter();

            segment = new OperationSegment(possibleFunctions, null /*entitySet*/);
            return(true);
        }
예제 #20
0
        /// <summary>
        /// Generate an expand item (and a select item for the implicit nav prop if necessary) based on an ExpandTermToken
        /// </summary>
        /// <param name="tokenIn">the expandTerm token to visit</param>
        /// <returns>the expand item for this expand term token.</returns>
        private SelectItem GenerateExpandItem(ExpandTermToken tokenIn)
        {
            ExceptionUtils.CheckArgumentNotNull(tokenIn, "tokenIn");

            PathSegmentToken currentToken = tokenIn.PathToNavigationProp;

            IEdmStructuredType      currentLevelEntityType = this.EdmType;
            List <ODataPathSegment> pathSoFar         = new List <ODataPathSegment>();
            PathSegmentToken        firstNonTypeToken = currentToken;

            if (currentToken.IsNamespaceOrContainerQualified())
            {
                pathSoFar.AddRange(SelectExpandPathBinder.FollowTypeSegments(currentToken, this.Model, this.Settings.SelectExpandLimit, this.configuration.Resolver, ref currentLevelEntityType, out firstNonTypeToken));
            }

            IEdmProperty edmProperty = this.configuration.Resolver.ResolveProperty(currentLevelEntityType, firstNonTypeToken.Identifier);

            if (edmProperty == null)
            {
                throw new ODataException(ODataErrorStrings.MetadataBinder_PropertyNotDeclared(currentLevelEntityType.FullTypeName(), currentToken.Identifier));
            }

            IEdmNavigationProperty currentNavProp     = edmProperty as IEdmNavigationProperty;
            IEdmStructuralProperty currentComplexProp = edmProperty as IEdmStructuralProperty;

            if (currentNavProp == null && currentComplexProp == null)
            {
                throw new ODataException(ODataErrorStrings.ExpandItemBinder_PropertyIsNotANavigationPropertyOrComplexProperty(currentToken.Identifier, currentLevelEntityType.FullTypeName()));
            }

            if (currentComplexProp != null)
            {
                currentNavProp = ParseComplexTypesBeforeNavigation(currentComplexProp, ref firstNonTypeToken, pathSoFar);
            }

            // ensure that we're always dealing with proper V4 syntax
            if (firstNonTypeToken.NextToken != null && firstNonTypeToken.NextToken.NextToken != null)
            {
                throw new ODataException(ODataErrorStrings.ExpandItemBinder_TraversingMultipleNavPropsInTheSamePath);
            }

            bool isRef = false;

            if (firstNonTypeToken.NextToken != null)
            {
                // lastly... make sure that, since we're on a NavProp, that the next token isn't null.
                if (firstNonTypeToken.NextToken.Identifier == UriQueryConstants.RefSegment)
                {
                    isRef = true;
                }
                else
                {
                    throw new ODataException(ODataErrorStrings.ExpandItemBinder_TraversingMultipleNavPropsInTheSamePath);
                }
            }

            // Add the segments in select and expand to parsed segments
            List <ODataPathSegment> parsedPath = new List <ODataPathSegment>(this.parsedSegments);

            parsedPath.AddRange(pathSoFar);

            IEdmNavigationSource targetNavigationSource = null;

            if (this.NavigationSource != null)
            {
                IEdmPathExpression bindingPath;
                targetNavigationSource = this.NavigationSource.FindNavigationTarget(currentNavProp, BindingPathHelper.MatchBindingPath, parsedPath, out bindingPath);
            }

            NavigationPropertySegment navSegment = new NavigationPropertySegment(currentNavProp, targetNavigationSource);

            pathSoFar.Add(navSegment);
            parsedPath.Add(navSegment); // Add the navigation property segment to parsed segments for future usage.
            ODataExpandPath pathToNavProp = new ODataExpandPath(pathSoFar);

            // $apply
            ApplyClause applyOption = BindApply(tokenIn.ApplyOptions, targetNavigationSource);

            // $compute
            ComputeClause computeOption = BindCompute(tokenIn.ComputeOption, targetNavigationSource);

            var  generatedProperties = GetGeneratedProperties(computeOption, applyOption);
            bool collapsed           = applyOption?.Transformations.Any(t => t.Kind == TransformationNodeKind.Aggregate || t.Kind == TransformationNodeKind.GroupBy) ?? false;

            // $filter
            FilterClause filterOption = BindFilter(tokenIn.FilterOption, targetNavigationSource, null, generatedProperties, collapsed);

            // $orderby
            OrderByClause orderbyOption = BindOrderby(tokenIn.OrderByOptions, targetNavigationSource, null, generatedProperties, collapsed);

            // $search
            SearchClause searchOption = BindSearch(tokenIn.SearchOption, targetNavigationSource, null);

            if (isRef)
            {
                return(new ExpandedReferenceSelectItem(pathToNavProp, targetNavigationSource, filterOption, orderbyOption, tokenIn.TopOption, tokenIn.SkipOption, tokenIn.CountQueryOption, searchOption, computeOption, applyOption));
            }

            // $select & $expand
            SelectExpandClause subSelectExpand = BindSelectExpand(tokenIn.ExpandOption, tokenIn.SelectOption, parsedPath, targetNavigationSource, null, generatedProperties, collapsed);

            // $levels
            LevelsClause levelsOption = ParseLevels(tokenIn.LevelsOption, currentLevelEntityType, currentNavProp);

            return(new ExpandedNavigationSelectItem(pathToNavProp,
                                                    targetNavigationSource, subSelectExpand, filterOption, orderbyOption, tokenIn.TopOption, tokenIn.SkipOption, tokenIn.CountQueryOption, searchOption, levelsOption, computeOption, applyOption));
        }
예제 #21
0
        /// <summary>
        /// Building of a PathSegmentToken, continue parsing any expand options (nested $filter, $expand, etc)
        /// to build up an ExpandTermToken which fully represents the tree that makes up this expand term.
        /// </summary>
        /// <param name="pathToken">The PathSegmentToken representing the parsed expand path whose options we are now parsing.</param>
        /// <param name="optionsText">A string of the text between the parenthesis after an expand option.</param>
        /// <returns>The list of expand term tokens based on the path token, and all available expand options.</returns>
        internal List <ExpandTermToken> BuildExpandTermToken(PathSegmentToken pathToken, string optionsText)
        {
            // Setup a new lexer for parsing the optionsText
            this.lexer = new ExpressionLexer(optionsText ?? "", true /*moveToFirstToken*/, true /*useSemicolonDelimiter*/);

            // $expand option with star only support $ref option, $expand option property could be "*" or "*/$ref", special logic will be adopted.
            if (pathToken.Identifier == UriQueryConstants.Star || (pathToken.Identifier == UriQueryConstants.RefSegment && pathToken.NextToken.Identifier == UriQueryConstants.Star))
            {
                return(BuildStarExpandTermToken(pathToken));
            }

            QueryToken filterOption = null;
            IEnumerable <OrderByToken> orderByOptions = null;
            long?                    topOption        = null;
            long?                    skipOption       = null;
            bool?                    countOption      = null;
            long?                    levelsOption     = null;
            QueryToken               searchOption     = null;
            SelectToken              selectOption     = null;
            ExpandToken              expandOption     = null;
            ComputeToken             computeOption    = null;
            IEnumerable <QueryToken> applyOptions     = null;

            if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen)
            {
                // advance past the '('
                this.lexer.NextToken();

                // Check for (), which is not allowed.
                if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen)
                {
                    throw new ODataException(ODataErrorStrings.UriParser_MissingExpandOption(pathToken.Identifier));
                }

                // Look for all the supported query options
                while (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen)
                {
                    string text = this.enableCaseInsensitiveBuiltinIdentifier
                        ? this.lexer.CurrentToken.Text.ToLowerInvariant()
                        : this.lexer.CurrentToken.Text;

                    // Prepend '$' prefix if needed.
                    if (this.enableNoDollarQueryOptions && !text.StartsWith(UriQueryConstants.DollarSign, StringComparison.Ordinal))
                    {
                        text = string.Format(CultureInfo.InvariantCulture, "{0}{1}", UriQueryConstants.DollarSign, text);
                    }

                    switch (text)
                    {
                    case ExpressionConstants.QueryOptionFilter:     // inner $filter
                        filterOption = ParseInnerFilter();
                        break;

                    case ExpressionConstants.QueryOptionOrderby:     // inner $orderby
                        orderByOptions = ParseInnerOrderBy();
                        break;

                    case ExpressionConstants.QueryOptionTop:     // inner $top
                        topOption = ParseInnerTop();
                        break;

                    case ExpressionConstants.QueryOptionSkip:     // innner $skip
                        skipOption = ParseInnerSkip();
                        break;

                    case ExpressionConstants.QueryOptionCount:     // inner $count
                        countOption = ParseInnerCount();
                        break;

                    case ExpressionConstants.QueryOptionSearch:     // inner $search
                        searchOption = ParseInnerSearch();
                        break;

                    case ExpressionConstants.QueryOptionLevels:     // inner $level
                        levelsOption = ParseInnerLevel();
                        break;

                    case ExpressionConstants.QueryOptionSelect:     // inner $select
                        selectOption = ParseInnerSelect(pathToken);
                        break;

                    case ExpressionConstants.QueryOptionExpand:     // inner $expand
                        expandOption = ParseInnerExpand(pathToken);
                        break;

                    case ExpressionConstants.QueryOptionCompute:     // inner $compute
                        computeOption = ParseInnerCompute();
                        break;

                    case ExpressionConstants.QueryOptionApply:     // inner $apply
                        applyOptions = ParseInnerApply();
                        break;

                    default:
                    {
                        throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText));
                    }
                    }
                }

                // Move past the ')'
                this.lexer.NextToken();
            }

            // Either there was no '(' at all or we just read past the ')' so we should be at the end
            if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End)
            {
                throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText));
            }

            // TODO, there should be some check here in case pathToken identifier is $ref, select, expand and levels options are not allowed.
            List <ExpandTermToken> expandTermTokenList = new List <ExpandTermToken>();
            ExpandTermToken        currentToken        = new ExpandTermToken(pathToken, filterOption, orderByOptions, topOption,
                                                                             skipOption, countOption, levelsOption, searchOption, selectOption, expandOption, computeOption, applyOptions);

            expandTermTokenList.Add(currentToken);

            return(expandTermTokenList);
        }
예제 #22
0
        /// <summary>
        /// Building off of a PathSegmentToken whose value is star, only nested level options is allowed.
        /// </summary>
        /// <param name="pathToken">The PathSegmentToken representing the parsed expand path whose options we are now parsing.</param>
        /// <returns>An expand term token based on the path token, and all available expand options.</returns>
        private List <ExpandTermToken> BuildStarExpandTermToken(PathSegmentToken pathToken)
        {
            List <ExpandTermToken> expandTermTokenList = new List <ExpandTermToken>();
            long?levelsOption = null;
            bool isRefExpand  = (pathToken.Identifier == UriQueryConstants.RefSegment);

            // Based on the specification,
            //   For star in expand, this will be supported,
            //   $expand=*
            //   $expand=EntitySet($expand=* )
            //   $expand=*/$ref
            //   $expand=*,EntitySet
            //   $expand=EntitySet, *
            //   $expand=*/$ref,EntitySet
            //   Parenthesized set of expand options for star expand option supported are $level per specification.
            //   And this will throw exception,
            //   $expand= * /$count
            //   Parenthesized set of expand options for star expand option which will also cause exception are $filter, $select, $orderby, $skip, $top, $count, $search, and $expand per specification.
            // And level is not supported with "*/$ref".

            // As 2016/1/8, the navigation property is only supported in entity type, and will support in ComplexType in future.
            if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen)
            {
                // advance past the '('
                this.lexer.NextToken();

                // Check for (), which is not allowed.
                if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen)
                {
                    throw new ODataException(ODataErrorStrings.UriParser_MissingExpandOption(pathToken.Identifier));
                }

                // Only level option is supported by expand.
                while (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen)
                {
                    string text = this.enableCaseInsensitiveBuiltinIdentifier
                        ? this.lexer.CurrentToken.Text.ToLowerInvariant()
                        : this.lexer.CurrentToken.Text;
                    switch (text)
                    {
                    case ExpressionConstants.QueryOptionLevels:
                    {
                        if (!isRefExpand)
                        {
                            levelsOption = ParseInnerLevel();
                        }
                        else
                        {
                            // no option is allowed when expand with star per specification
                            throw new ODataException(ODataErrorStrings.UriExpandParser_TermIsNotValidForStarRef(this.lexer.ExpressionText));
                        }

                        break;
                    }

                    default:
                    {
                        throw new ODataException(ODataErrorStrings.UriExpandParser_TermIsNotValidForStar(this.lexer.ExpressionText));
                    }
                    }
                }

                // Move past the ')'
                this.lexer.NextToken();
            }

            // Either there was no '(' at all or we just read past the ')' so we should be at the end
            if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End)
            {
                throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText));
            }

            // As 2016/1/8, the navigation property is only supported in entity type, and will support in ComplexType in future.
            var entityType = this.parentStructuredType as IEdmEntityType;

            if (entityType == null)
            {
                throw new ODataException(ODataErrorStrings.UriExpandParser_ParentEntityIsNull(this.lexer.ExpressionText));
            }

            foreach (var navigationProperty in entityType.NavigationProperties())
            {
                var tmpPathToken = default(PathSegmentToken);

                // create path token for each navigation properties.
                if (pathToken.Identifier.Equals(UriQueryConstants.RefSegment))
                {
                    tmpPathToken = new NonSystemToken(navigationProperty.Name, null, pathToken.NextToken.NextToken);
                    tmpPathToken = new NonSystemToken(UriQueryConstants.RefSegment, null, tmpPathToken);
                }
                else
                {
                    tmpPathToken = new NonSystemToken(navigationProperty.Name, null, pathToken.NextToken);
                }

                ExpandTermToken currentToken = new ExpandTermToken(tmpPathToken, null, null,
                                                                   null, null, null, levelsOption, null, null, null, null, null);
                expandTermTokenList.Add(currentToken);
            }

            return(expandTermTokenList);
        }
예제 #23
0
        /// <summary>
        /// Process a <see cref="PathSegmentToken"/> following any type segments if necessary.
        /// </summary>
        /// <param name="tokenIn">the path token to process.</param>
        private List <ODataPathSegment> ProcessSelectTokenPath(PathSegmentToken tokenIn)
        {
            Debug.Assert(tokenIn != null, "tokenIn != null");

            List <ODataPathSegment> pathSoFar        = new List <ODataPathSegment>();
            IEdmStructuredType      currentLevelType = this.edmType;

            // first, walk through all type segments in a row, converting them from tokens into segments.
            if (tokenIn.IsNamespaceOrContainerQualified() && !UriParserHelper.IsAnnotation(tokenIn.Identifier))
            {
                PathSegmentToken firstNonTypeToken;
                pathSoFar.AddRange(SelectExpandPathBinder.FollowTypeSegments(tokenIn, this.Model, this.Settings.SelectExpandLimit, this.configuration.Resolver, ref currentLevelType, out firstNonTypeToken));
                Debug.Assert(firstNonTypeToken != null, "Did not get last token.");
                tokenIn = firstNonTypeToken as NonSystemToken;
                if (tokenIn == null)
                {
                    throw new ODataException(ODataErrorStrings.SelectExpandBinder_SystemTokenInSelect(firstNonTypeToken.Identifier));
                }
            }

            // next, create a segment for the first non-type segment in the path.
            ODataPathSegment lastSegment = SelectPathSegmentTokenBinder.ConvertNonTypeTokenToSegment(tokenIn, this.Model, currentLevelType, this.configuration.Resolver, this.state);

            // next, create an ODataPath and add the segments to it.
            if (lastSegment != null)
            {
                pathSoFar.Add(lastSegment);

                // try create a complex type property path.
                while (true)
                {
                    // no need to go on if the current property is not of complex type or collection of complex type,
                    // unless the segment is a primitive type cast or a property on an open complex property.
                    currentLevelType = lastSegment.EdmType as IEdmStructuredType;
                    IEdmCollectionType collectionType = lastSegment.EdmType as IEdmCollectionType;
                    IEdmPrimitiveType  primitiveType  = lastSegment.EdmType as IEdmPrimitiveType;
                    DynamicPathSegment dynamicPath    = lastSegment as DynamicPathSegment;
                    if ((currentLevelType == null || currentLevelType.TypeKind != EdmTypeKind.Complex) &&
                        (collectionType == null || collectionType.ElementType.TypeKind() != EdmTypeKind.Complex) &&
                        (primitiveType == null || primitiveType.TypeKind != EdmTypeKind.Primitive) &&
                        (dynamicPath == null || tokenIn.NextToken == null))
                    {
                        break;
                    }

                    NonSystemToken nextToken = tokenIn.NextToken as NonSystemToken;
                    if (nextToken == null)
                    {
                        break;
                    }

                    if (UriParserHelper.IsAnnotation(nextToken.Identifier))
                    {
                        lastSegment = SelectPathSegmentTokenBinder.ConvertNonTypeTokenToSegment(nextToken, this.Model,
                                                                                                currentLevelType, this.configuration.Resolver, null);
                    }
                    else if (primitiveType == null && dynamicPath == null)
                    {
                        // This means last segment a collection of complex type,
                        // current segment can only be type cast and cannot be property name.
                        if (currentLevelType == null)
                        {
                            currentLevelType = collectionType.ElementType.Definition as IEdmStructuredType;
                        }

                        // If there is no collection type in the path yet, will try to bind property for the next token
                        // first try bind the segment as property.
                        lastSegment = SelectPathSegmentTokenBinder.ConvertNonTypeTokenToSegment(nextToken, this.Model,
                                                                                                currentLevelType, this.configuration.Resolver, null);
                    }
                    else
                    {
                        // determine whether we are looking at a type cast or a dynamic path segment.
                        EdmPrimitiveTypeKind nextTypeKind = EdmCoreModel.Instance.GetPrimitiveTypeKind(nextToken.Identifier);
                        IEdmPrimitiveType    castType     = EdmCoreModel.Instance.GetPrimitiveType(nextTypeKind);
                        if (castType != null)
                        {
                            lastSegment = new TypeSegment(castType, castType, null);
                        }
                        else if (dynamicPath != null)
                        {
                            lastSegment = new DynamicPathSegment(nextToken.Identifier);
                        }
                        else
                        {
                            throw new ODataException(ODataErrorStrings.SelectBinder_MultiLevelPathInSelect);
                        }
                    }

                    // then try bind the segment as type cast.
                    if (lastSegment == null)
                    {
                        IEdmStructuredType typeFromNextToken =
                            UriEdmHelpers.FindTypeFromModel(this.Model, nextToken.Identifier, this.configuration.Resolver) as
                            IEdmStructuredType;

                        if (typeFromNextToken.IsOrInheritsFrom(currentLevelType))
                        {
                            lastSegment = new TypeSegment(typeFromNextToken, /*entitySet*/ null);
                        }
                    }

                    // type cast failed too.
                    if (lastSegment == null)
                    {
                        break;
                    }

                    // try move to and add next path segment.
                    tokenIn = nextToken;
                    pathSoFar.Add(lastSegment);
                }
            }

            // non-navigation cases do not allow further segments in $select.
            if (tokenIn.NextToken != null)
            {
                throw new ODataException(ODataErrorStrings.SelectBinder_MultiLevelPathInSelect);
            }

            // Later, we can consider to create a "DynamicOperationSegment" to handle this.
            // But now, Let's throw exception.
            if (lastSegment == null)
            {
                throw new ODataException(ODataErrorStrings.MetadataBinder_InvalidIdentifierInQueryOption(tokenIn.Identifier));
            }

            // navigation property is not allowed to append sub path in the selection.
            NavigationPropertySegment navPropSegment = pathSoFar.LastOrDefault() as NavigationPropertySegment;

            if (navPropSegment != null && tokenIn.NextToken != null)
            {
                throw new ODataException(ODataErrorStrings.SelectBinder_MultiLevelPathInSelect);
            }

            return(pathSoFar);
        }
예제 #24
0
        private IEdmNavigationProperty ParseComplexTypesBeforeNavigation(IEdmStructuralProperty edmProperty, ref PathSegmentToken currentToken, List <ODataPathSegment> pathSoFar)
        {
            pathSoFar.Add(new PropertySegment(edmProperty));

            if (currentToken.NextToken == null)
            {
                throw new ODataException(ODataErrorStrings.ExpandItemBinder_PropertyIsNotANavigationPropertyOrComplexProperty(currentToken.Identifier, edmProperty.DeclaringType.FullTypeName()));
            }

            currentToken = currentToken.NextToken;

            IEdmType complexType = edmProperty.Type.Definition;

            IEdmCollectionType collectionType = complexType as IEdmCollectionType;

            if (collectionType != null)
            {
                complexType = collectionType.ElementType.Definition;
            }

            IEdmStructuredType currentType = complexType as IEdmStructuredType;

            if (currentType == null)
            {
                throw new ODataException(ODataErrorStrings.ExpandItemBinder_InvaidSegmentInExpand(currentToken.Identifier));
            }

            if (currentToken.IsNamespaceOrContainerQualified())
            {
                pathSoFar.AddRange(SelectExpandPathBinder.FollowTypeSegments(currentToken, this.Model, this.Settings.SelectExpandLimit, this.configuration.Resolver, ref currentType, out currentToken));
            }

            IEdmProperty property = this.configuration.Resolver.ResolveProperty(currentType, currentToken.Identifier);

            if (edmProperty == null)
            {
                throw new ODataException(ODataErrorStrings.MetadataBinder_PropertyNotDeclared(currentType.FullTypeName(), currentToken.Identifier));
            }

            IEdmStructuralProperty complexProp = property as IEdmStructuralProperty;

            if (complexProp != null)
            {
                property = ParseComplexTypesBeforeNavigation(complexProp, ref currentToken, pathSoFar);
            }

            IEdmNavigationProperty navProp = property as IEdmNavigationProperty;

            if (navProp != null)
            {
                return(navProp);
            }
            else
            {
                throw new ODataException(ODataErrorStrings.ExpandItemBinder_PropertyIsNotANavigationPropertyOrComplexProperty(currentToken.Identifier, currentType.FullTypeName()));
            }
        }
예제 #25
0
        /// <summary>
        /// Generate an expand item (and a select item for the implicit nav prop if necessary) based on an ExpandTermToken
        /// </summary>
        /// <param name="tokenIn">the expandTerm token to visit</param>
        /// <returns>the expand item for this expand term token.</returns>
        private SelectItem GenerateExpandItem(ExpandTermToken tokenIn)
        {
            ExceptionUtils.CheckArgumentNotNull(tokenIn, "tokenIn");

            PathSegmentToken currentToken = tokenIn.PathToNavigationProp;

            IEdmStructuredType      currentLevelEntityType = this.EdmType;
            List <ODataPathSegment> pathSoFar         = new List <ODataPathSegment>();
            PathSegmentToken        firstNonTypeToken = currentToken;

            if (currentToken.IsNamespaceOrContainerQualified())
            {
                pathSoFar.AddRange(SelectExpandPathBinder.FollowTypeSegments(currentToken, this.Model, this.Settings.SelectExpandLimit, this.configuration.Resolver, ref currentLevelEntityType, out firstNonTypeToken));
            }

            IEdmProperty edmProperty = this.configuration.Resolver.ResolveProperty(currentLevelEntityType, firstNonTypeToken.Identifier);

            if (edmProperty == null)
            {
                throw new ODataException(ODataErrorStrings.MetadataBinder_PropertyNotDeclared(currentLevelEntityType.FullTypeName(), currentToken.Identifier));
            }

            IEdmNavigationProperty currentNavProp     = edmProperty as IEdmNavigationProperty;
            IEdmStructuralProperty currentComplexProp = edmProperty as IEdmStructuralProperty;

            if (currentNavProp == null && currentComplexProp == null)
            {
                throw new ODataException(ODataErrorStrings.ExpandItemBinder_PropertyIsNotANavigationPropertyOrComplexProperty(currentToken.Identifier, currentLevelEntityType.FullTypeName()));
            }

            if (currentComplexProp != null)
            {
                currentNavProp = ParseComplexTypesBeforeNavigation(currentComplexProp, ref firstNonTypeToken, pathSoFar);
            }

            // ensure that we're always dealing with proper V4 syntax
            if (firstNonTypeToken.NextToken != null && firstNonTypeToken.NextToken.NextToken != null)
            {
                throw new ODataException(ODataErrorStrings.ExpandItemBinder_TraversingMultipleNavPropsInTheSamePath);
            }

            bool isRef = false;

            if (firstNonTypeToken.NextToken != null)
            {
                // lastly... make sure that, since we're on a NavProp, that the next token isn't null.
                if (firstNonTypeToken.NextToken.Identifier == UriQueryConstants.RefSegment)
                {
                    isRef = true;
                }
                else
                {
                    throw new ODataException(ODataErrorStrings.ExpandItemBinder_TraversingMultipleNavPropsInTheSamePath);
                }
            }

            // Add the segments in select and expand to parsed segments
            this.parsedSegments.AddRange(pathSoFar);

            IEdmNavigationSource targetNavigationSource = null;

            if (this.NavigationSource != null)
            {
                IEdmPathExpression bindingPath;
                targetNavigationSource = this.NavigationSource.FindNavigationTarget(currentNavProp, BindingPathHelper.MatchBindingPath, this.parsedSegments, out bindingPath);
            }

            NavigationPropertySegment navSegment = new NavigationPropertySegment(currentNavProp, targetNavigationSource);

            pathSoFar.Add(navSegment);
            this.parsedSegments.Add(navSegment);   // Add the navigation property segment to parsed segments for future usage.
            ODataExpandPath pathToNavProp = new ODataExpandPath(pathSoFar);

            // call MetadataBinder to build the filter clause
            FilterClause filterOption = null;

            if (tokenIn.FilterOption != null)
            {
                MetadataBinder binder       = this.BuildNewMetadataBinder(targetNavigationSource);
                FilterBinder   filterBinder = new FilterBinder(binder.Bind, binder.BindingState);
                filterOption = filterBinder.BindFilter(tokenIn.FilterOption);
            }

            // call MetadataBinder again to build the orderby clause
            OrderByClause orderbyOption = null;

            if (tokenIn.OrderByOptions != null)
            {
                MetadataBinder binder        = this.BuildNewMetadataBinder(targetNavigationSource);
                OrderByBinder  orderByBinder = new OrderByBinder(binder.Bind);
                orderbyOption = orderByBinder.BindOrderBy(binder.BindingState, tokenIn.OrderByOptions);
            }

            SearchClause searchOption = null;

            if (tokenIn.SearchOption != null)
            {
                MetadataBinder binder       = this.BuildNewMetadataBinder(targetNavigationSource);
                SearchBinder   searchBinder = new SearchBinder(binder.Bind);
                searchOption = searchBinder.BindSearch(tokenIn.SearchOption);
            }

            if (isRef)
            {
                return(new ExpandedReferenceSelectItem(pathToNavProp, targetNavigationSource, filterOption, orderbyOption, tokenIn.TopOption, tokenIn.SkipOption, tokenIn.CountQueryOption, searchOption));
            }

            SelectExpandClause subSelectExpand;

            if (tokenIn.ExpandOption != null)
            {
                subSelectExpand = this.GenerateSubExpand(tokenIn);
            }
            else
            {
                subSelectExpand = BuildDefaultSubExpand();
            }

            subSelectExpand = this.DecorateExpandWithSelect(subSelectExpand, currentNavProp, tokenIn.SelectOption);

            LevelsClause levelsOption = this.ParseLevels(tokenIn.LevelsOption, currentLevelEntityType, currentNavProp);

            return(new ExpandedNavigationSelectItem(pathToNavProp, targetNavigationSource, subSelectExpand, filterOption, orderbyOption, tokenIn.TopOption, tokenIn.SkipOption, tokenIn.CountQueryOption, searchOption, levelsOption));
        }