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