/// <summary>Walks the subtree of this node and removes all nodes which were not marked projected.</summary>
        /// <remarks>Used to remove unnecessary expanded nodes.</remarks>
        internal void RemoveNonProjectedNodes()
        {
            for (int j = this.nodes.Count - 1; j >= 0; j--)
            {
                ExpandedProjectionNode expandedNode = this.nodes[j] as ExpandedProjectionNode;

                // Leave non-expanded properties there as they specify projections.
                if (expandedNode == null)
                {
                    continue;
                }

                // If we are to project entire subtree, leave all expanded nodes in as well
                // otherwise remove the expanded nodes which are not marked as projected.
                if (!this.projectSubtree && !expandedNode.ProjectionFound)
                {
                    // This removes the expandedNode from the tree (and all its children)
                    this.nodes.RemoveAt(j);
                }
                else
                {
                    expandedNode.RemoveNonProjectedNodes();
                }
            }
        }
        /// <summary>Marks the entire subtree as projected.</summary>
        /// <remarks>This is used when there were no projections specified in the query
        /// to mark the entire tree as projected.</remarks>
        internal void MarkSubtreeAsProjected()
        {
            this.projectSubtree = true;
            this.projectAllImmediateProperties = false;

            foreach (ProjectionNode node in this.nodes)
            {
                ExpandedProjectionNode expandedNode = node as ExpandedProjectionNode;
                if (expandedNode != null)
                {
                    expandedNode.MarkSubtreeAsProjected();
                }
            }
        }
        /// <summary>
        /// Add the projection node with the given property name, property instance and target resource type, if required.
        /// </summary>
        /// <param name="propertyName">Name of the property that needs to be projected.</param>
        /// <param name="property">ResourceProperty instance containing information about the property - this will be null for open properties.</param>
        /// <param name="targetResourceType">ResourceType instance on which the property needs to be expanded.</param>
        /// <returns>A new or an existing instance of ProjectionNode.</returns>
        internal ExpandedProjectionNode AddProjectionNode(string propertyName, ResourceProperty property, ResourceType targetResourceType)
        {
            // find out if there exists another node with the given property name.
            ProjectionNode node = this.FindNode(propertyName);

            if (node == null || !ExpandedProjectionNode.ApplyPropertyToExistingNode(node, property, targetResourceType))
            {
                // just create a simple projection node for it
                node = new ProjectionNode(propertyName, property, targetResourceType);
                this.AddNode(node);
            }

            return(node as ExpandedProjectionNode);
        }
        /// <summary>
        /// Whether the given property can be applied to the existing node.
        /// </summary>
        /// <param name="existingNode">Existing node with the same property name.</param>
        /// <param name="property">ResourceProperty instance for the property refered by the new segment.</param>
        /// <param name="targetResourceType">TargetResourceType for the new segment.</param>
        /// <returns>true if the given property can be applied to the existing node, otherwise returns false.</returns>
        /// <remarks>In case this function returns true, it might modify the TargetResourceType property of the existing node.</remarks>
        private static bool ApplyPropertyToExistingNode(ProjectionNode existingNode, ResourceProperty property, ResourceType targetResourceType)
        {
            Debug.Assert(existingNode != null, "existingNode != null");
            Debug.Assert(property == null || property.Name == existingNode.PropertyName, "property == null || property.Name == existingNode.PropertyName");

            // This is just quicker way of determining if declared property belongs to different sub-trees.
            // Since any given property can be declared once in a chain of derived types, if the property instances
            // are not the same, we can assume that they do not belong to the same hierarchy chain.
            // Even if we do not have this check, everything will just work, since we check the type assignability.
            if (property != null && existingNode.Property != null && property != existingNode.Property)
            {
                return(false);
            }

            ExpandedProjectionNode expansionNode = existingNode as ExpandedProjectionNode;

            // If the target resource type of the new segment is a super type, we might
            // need to update the target resource type of the existing node.
            // Since IsAssignableFrom returns true when they are equal, it isn't require
            // to do anything, but if we do, it should still work.
            if (targetResourceType.IsAssignableFrom(existingNode.TargetResourceType))
            {
                ExpandedProjectionNode.VerifyPropertyMismatchAndExpandSelectMismatchScenario(
                    existingNode,
                    property,
                    targetResourceType,
                    expansionNode != null);

                existingNode.TargetResourceType = targetResourceType;
                return(true);
            }

            // If the existing node is already a super type, then there is nothing to do
            // other than validating the error scenarios.
            if (existingNode.TargetResourceType.IsAssignableFrom(targetResourceType))
            {
                ExpandedProjectionNode.VerifyPropertyMismatchAndExpandSelectMismatchScenario(
                    existingNode,
                    property,
                    targetResourceType,
                    expansionNode != null);
                return(true);
            }

            return(false);
        }
        /// <summary>
        /// Adds a new expanded node with the given segment name, if required.
        /// </summary>
        /// <param name="segment">Expand segment as specified in the uri.</param>
        /// <param name="childSelectExpandClause">The select expand clause for the current node from the URI Parser.</param>
        /// <returns>An instance of ExpandedProjectionNode - either new or existing one.</returns>
        internal ExpandedProjectionNode AddExpandedNode(ExpandSegment segment, SelectExpandClause childSelectExpandClause)
        {
            Debug.Assert(segment.ExpandedProperty != null, "we do not allow expands on open properties");
            Debug.Assert(childSelectExpandClause != null, "childSelectExpandClause != null");

            ExpandedProjectionNode childNode = (ExpandedProjectionNode)this.FindNode(segment.Name);

            // Currently, since we do not have a back pointer in ResourceProperty for the declaring type,
            // the same resource property instance can belong to 2 completely different types.
            // Hence doing reference equality on Resource Property instance does not gaurantee that they
            // refer to the same property. But if the references are not equal, they are gauranteed to be
            // different. The check of reference equality is to make sure we do the expensive IsAssignableFrom
            // check, only if the instances are the same, and if the IsAssignable returns true, then we are
            // gauranteed that the property refers to the same property.
            if (childNode != null && childNode.Property == segment.ExpandedProperty)
            {
                // If the given segment specifies the same property on a super type (or an ancestor),
                // then we need to change the target type on the segment to the super type (or ancestor),
                // otherwise we should ignore the segment.
                // Since IsAssignableFrom returns true, if the instances are the same, we will update the
                // target resource type to the same instance. It doesn't matter, but having another check
                // for reference equality makes code less readable.
                if (segment.TargetResourceType.IsAssignableFrom(childNode.TargetResourceType))
                {
                    childNode.TargetResourceType = segment.TargetResourceType;
                }
            }
            else
            {
                childNode = new ExpandedProjectionNode(
                    segment.Name,
                    segment.ExpandedProperty,
                    segment.TargetResourceType,
                    segment.Container,
                    segment.OrderingInfo,
                    segment.Filter,
                    null,
                    segment.Container.PageSize != 0 ? (int?)segment.Container.PageSize : null,
                    segment.MaxResultsExpected != Int32.MaxValue ? (int?)segment.MaxResultsExpected : null,
                    childSelectExpandClause);
                this.AddNode(childNode);
            }

            return(childNode);
        }
        /// <summary>Adds a new child node to this node.</summary>
        /// <param name="node">The child node to add.</param>
        private void AddNode(ProjectionNode node)
        {
            Debug.Assert(node != null, "node != null");
            Debug.Assert(
                this.nodes.Count(
                    n => n.PropertyName == node.PropertyName &&
                    (n.TargetResourceType.IsAssignableFrom(node.TargetResourceType) || node.TargetResourceType.IsAssignableFrom(n.TargetResourceType))) == 0,
                "make sure there is no node with the same property name in the inheritance hierarchy");

            this.nodes.Add(node);

            ExpandedProjectionNode expandedNode = node as ExpandedProjectionNode;

            if (expandedNode != null)
            {
                this.hasExpandedPropertyOnDerivedType = this.hasExpandedPropertyOnDerivedType || (!ResourceType.CompareReferences(this.ResourceType, node.TargetResourceType));
            }
        }
        /// <summary>
        /// Adds a new expanded node with the given segment name, if required.
        /// </summary>
        /// <param name="segment">Expand segment as specified in the uri.</param>
        /// <param name="childSelectExpandClause">The select expand clause for the current node from the URI Parser.</param>
        /// <returns>An instance of ExpandedProjectionNode - either new or existing one.</returns>
        internal ExpandedProjectionNode AddExpandedNode(ExpandSegment segment, SelectExpandClause childSelectExpandClause)
        {
            Debug.Assert(segment.ExpandedProperty != null, "we do not allow expands on open properties");
            Debug.Assert(childSelectExpandClause != null, "childSelectExpandClause != null");

            ExpandedProjectionNode childNode = (ExpandedProjectionNode)this.FindNode(segment.Name);

            // Currently, since we do not have a back pointer in ResourceProperty for the declaring type,
            // the same resource property instance can belong to 2 completely different types.
            // Hence doing reference equality on Resource Property instance does not gaurantee that they
            // refer to the same property. But if the references are not equal, they are gauranteed to be
            // different. The check of reference equality is to make sure we do the expensive IsAssignableFrom
            // check, only if the instances are the same, and if the IsAssignable returns true, then we are
            // gauranteed that the property refers to the same property.
            if (childNode != null && childNode.Property == segment.ExpandedProperty)
            {
                // If the given segment specifies the same property on a super type (or an ancestor),
                // then we need to change the target type on the segment to the super type (or ancestor),
                // otherwise we should ignore the segment.
                // Since IsAssignableFrom returns true, if the instances are the same, we will update the
                // target resource type to the same instance. It doesn't matter, but having another check
                // for reference equality makes code less readable.
                if (segment.TargetResourceType.IsAssignableFrom(childNode.TargetResourceType))
                {
                    childNode.TargetResourceType = segment.TargetResourceType;
                }
            }
            else
            {
                childNode = new ExpandedProjectionNode(
                    segment.Name,
                    segment.ExpandedProperty,
                    segment.TargetResourceType,
                    segment.Container,
                    segment.OrderingInfo,
                    segment.Filter,
                    null,
                    segment.Container.PageSize != 0 ? (int?)segment.Container.PageSize : null,
                    segment.MaxResultsExpected != Int32.MaxValue ? (int?)segment.MaxResultsExpected : null,
                    childSelectExpandClause);
                this.AddNode(childNode);
            }

            return childNode;
        }
        /// <summary>
        /// Applies projections from the given select/expand clause to the tree represented by the given node.
        /// </summary>
        /// <param name="currentNode">The expand tree to apply projections to.</param>
        private void ApplyProjectionsToExpandTree(ExpandedProjectionNode currentNode)
        {
            // If this is the last segment in the path and it was a navigation property,
            // mark it to include the entire subtree
            if (currentNode.SelectExpandClause.AllSelected)
            {
                currentNode.ProjectionFound = true;
                currentNode.MarkSubtreeAsProjected();
            }

            foreach (var selectItem in currentNode.SelectExpandClause.SelectedItems)
            {
                currentNode.ProjectionFound = true;
             
                // '*' is special, it means "Project all immediate properties on this level."
                if (selectItem is WildcardSelectItem)
                {
                    currentNode.ProjectAllImmediateProperties = true;
                    continue;
                }

                if (selectItem is NamespaceQualifiedWildcardSelectItem)
                {
                    currentNode.ProjectAllImmediateOperations = true;
                    continue;
                }

                ODataPath path;

                var expandItem = selectItem as ExpandedNavigationSelectItem;
                if (expandItem != null)
                {
                    path = expandItem.PathToNavigationProperty;
                }
                else
                {
                    var pathSelectionItem = selectItem as PathSelectItem;
                    Debug.Assert(pathSelectionItem != null, "Unexpeced selection item type: " + selectItem.GetType());
                    path = pathSelectionItem.SelectedPath;
                }

                ExpandedProjectionNode childNode = this.ApplyPathSelection(path, currentNode);

                if (expandItem != null)
                {
                    if (childNode == null)
                    {
                        throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_ProjectedPropertyWithoutMatchingExpand(((NavigationPropertySegment)path.LastSegment).NavigationProperty.Name));
                    }

                    this.ApplyProjectionsToExpandTree(childNode);
                }
            }
        }
        /// <summary>
        /// Performs a depth-first walk down the expand tree, copying and adding to the current path as it goes. When the bottom of a path is reached, the path is added to the overall set of paths.
        /// </summary>
        /// <param name="currentPath">The current path so far for this depth-first traversal of the tree. Starts out null.</param>
        /// <param name="currentNode">The current node of the expand tree.</param>
        /// <param name="rootNode">The root node of the expand tree.</param>
        private void ExtractExpandPathSegmentCollections(ExpandSegmentCollection currentPath, ExpandedProjectionNode currentNode, RootProjectionNode rootNode)
        {
            ExpandSegmentCollection nextPath = null;
            foreach (ExpandedNavigationSelectItem expandItem in currentNode.SelectExpandClause.SelectedItems.OfType<ExpandedNavigationSelectItem>())
            {
                rootNode.ExpansionsSpecified = true;

                if (currentPath == null)
                {
                    nextPath = new ExpandSegmentCollection();
                }
                else
                {
                    // create a copy of the current path.
                    nextPath = new ExpandSegmentCollection(currentPath.Count);
                    nextPath.AddRange(currentPath);
                }

                var segment = this.CreateExpandSegment(expandItem, currentNode.ResourceType);
                nextPath.Add(segment);
                ExpandedProjectionNode childNode = currentNode.AddExpandedNode(segment, expandItem.SelectAndExpand);

                // if there are any derived expansions in the tree, set the rootNode.DerivedExpansionsSpecified to true.
                if (currentNode.HasExpandedPropertyOnDerivedType)
                {
                    rootNode.ExpansionOnDerivedTypesSpecified = true;
                }

                this.ExtractExpandPathSegmentCollections(nextPath, childNode, rootNode);
            }

            if (nextPath == null && currentPath != null)
            {
                this.expandPaths.Add(currentPath);
            }
        }
示例#10
0
        /// <summary>
        /// Applies the given path-based selection item to the given projection node.
        /// </summary>
        /// <param name="path">The path being selected.</param>
        /// <param name="currentNode">The current projection node.</param>
        /// <returns>A new or an existing instance of the ExpandedProjectionNode for path of the selection item.</returns>
        private ExpandedProjectionNode ApplyPathSelection(ODataPath path, ExpandedProjectionNode currentNode)
        {
            Debug.Assert(path != null, "path != null");

            ResourceType targetResourceType = this.GetTargetResourceTypeFromTypeSegments(path, currentNode.ResourceType);
            Debug.Assert(targetResourceType != null, "targetResourceType != null");

            ODataPathSegment lastSegment = path.LastSegment;
            Debug.Assert(lastSegment != null, "lastSegment != null");

            var operationSegment = lastSegment as OperationSegment;
            if (operationSegment != null)
            {
                ApplySelectionForOperations(operationSegment, currentNode, targetResourceType);
                return currentNode;
            }

            var openPropertySegment = lastSegment as OpenPropertySegment;
            if (openPropertySegment != null)
            {
                return ApplyProjectionForProperty(currentNode, openPropertySegment.PropertyName, null /*property*/, targetResourceType);
            }

            var propertySegment = lastSegment as PropertySegment;
            IEdmProperty edmProperty;
            if (propertySegment == null)
            {
                var navigationSegment = lastSegment as NavigationPropertySegment;
                Debug.Assert(navigationSegment != null, "Unexpected type of select segment: " + lastSegment.GetType());
                edmProperty = navigationSegment.NavigationProperty;
            }
            else
            {
                edmProperty = propertySegment.Property;
            }

            ResourceProperty resourceProperty = ((IResourcePropertyBasedEdmProperty)edmProperty).ResourceProperty;
            Debug.Assert(resourceProperty != null, "resourceProperty != null");

            // Look for any navigation properties that are hidden (EntitySetRights set to None)
            // If it is hidden, throw exception so we don't do any information disclousure
            // See Information disclosure: Astoria server allows 'hidden' navigation properties to be selected
            if (edmProperty.PropertyKind == EdmPropertyKind.Navigation && this.service.Provider.GetResourceSet(currentNode.ResourceSetWrapper, targetResourceType, resourceProperty) == null)
            {
                throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidPropertyNameSpecified(resourceProperty.Name, targetResourceType.FullName));
            }

            return ApplyProjectionForProperty(currentNode, resourceProperty.Name, resourceProperty, targetResourceType);
        }
示例#11
0
        /// <summary>
        /// Applies the selection for the given operation segment to the given projection node.
        /// </summary>
        /// <param name="operationSegment">The operation segment to apply to the projection node.</param>
        /// <param name="currentNode">The current projection node.</param>
        /// <param name="targetResourceType">The target type based on type segments.</param>
        private static void ApplySelectionForOperations(OperationSegment operationSegment, ExpandedProjectionNode currentNode, ResourceType targetResourceType)
        {
            IEnumerable<OperationWrapper> selectedOperations = operationSegment.Operations.Select(f =>
            {
                var metadataProviderEdmFunctionImport = f as MetadataProviderEdmOperation;
                Debug.Assert(metadataProviderEdmFunctionImport != null, "metadataProviderEdmFunctionImport != null");

                OperationWrapper operation = metadataProviderEdmFunctionImport.ServiceOperation;
                Debug.Assert(operation != null, "operation != null");
                return operation;
            });

            // Note that AddSelectedOperations will return false if the enumerable is empty.
            bool anyOperations = currentNode.SelectedOperations.AddSelectedOperations(targetResourceType, selectedOperations);
            Debug.Assert(anyOperations, "Operations segment should not have been created if no operations were found.");
        }
示例#12
0
        /// <summary>
        /// Apply projection for the given property to the parent projection node.
        /// </summary>
        /// <param name="parentNode">The parent node which the new node will be added to.</param>
        /// <param name="propertyName">Name of the property that needs to be projected.</param>
        /// <param name="property">ResourceProperty instance containing information about the property - this will be null for open properties.</param>
        /// <param name="targetResourceType">ResourceType instance on which the property needs to be expanded.</param>
        /// <returns>A new or an existing instance of the ExpandedProjectionNode for the given property.</returns>
        private static ExpandedProjectionNode ApplyProjectionForProperty(ExpandedProjectionNode parentNode, string propertyName, ResourceProperty property, ResourceType targetResourceType)
        {
            Debug.Assert(parentNode != null, "parentNode != null");
            Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)");
            Debug.Assert(targetResourceType != null, "targetResourceType != null");

            var childNode = parentNode.AddProjectionNode(propertyName, property, targetResourceType);
            Debug.Assert(
                childNode == null || childNode.ResourceType == property.ResourceType,
                "If we're traversing over a nav. property it's resource type must match the resource type of the expanded segment.");
            
            return childNode;
        }
示例#13
0
        /// <summary>Removes duplicates from the tree caused by wildcards and sorts the projected properties.</summary>
        /// <param name="provider">underlying provider instance.</param>
        /// <remarks>
        /// Examples
        /// $select=Orders, Orders/ID           - get rid of the Orders/ID
        /// $select=Orders, Orders/*            - get rid of the Orders/*
        /// $select=Orders/*, Orders/ID         - get rid of the Orders/ID
        /// $select=Orders/*, Orders/OrderItems&amp;$expand=Orders - get rid of the Orders/OrderItems (it's redundant to *)
        /// $select=Orders/*, Orders/OrderItems&amp;$expand=Orders/OrderItems - leave as is, the Orders/OrderItems are expanded
        ///
        /// The sorting order is the same as the order in which the properties are enumerated on the owning type.
        /// This is to preserve the same order as if no projections occured.
        /// </remarks>
        internal void ApplyWildcardsAndSort(DataServiceProviderWrapper provider)
        {
            // If this segment was marked to include entire subtree
            // simply remove all children which are not expanded
            // and propagate the information to all expanded children.
            if (this.projectSubtree)
            {
                for (int j = this.nodes.Count - 1; j >= 0; j--)
                {
                    ExpandedProjectionNode expandedNode = this.nodes[j] as ExpandedProjectionNode;
                    if (expandedNode != null)
                    {
                        expandedNode.projectSubtree = true;
                        expandedNode.ApplyWildcardsAndSort(provider);
                    }
                }

                this.projectAllImmediateProperties = false;
                this.projectAllImmediateOperations = false;
                return;
            }

            for (int j = this.nodes.Count - 1; j >= 0; j--)
            {
                ExpandedProjectionNode expandedNode = this.nodes[j] as ExpandedProjectionNode;

                // If this node was marked to include all immediate properties,
                //   remove all children which are not expanded.
                //   That means they are either simple properties or nav. properties which
                //   are not going to be expanded anyway.
                if (this.ProjectAllImmediateProperties && expandedNode == null)
                {
                    this.nodes.RemoveAt(j);
                }
                else if (expandedNode != null)
                {
                    expandedNode.ApplyWildcardsAndSort(provider);
                }
            }

            if (this.nodes.Count > 0)
            {
                // Sort the subsegments such that they have the same order as the properties
                //   on the owning resource type.

                // build the list of existing resource types that this query touches
                List <ResourceType> resourceTypes = new List <ResourceType>();
                resourceTypes.Add(this.ResourceType);

                // If we have one or more derived properties to expand or project,
                // we need to sort it based on the order in which the derived types
                // are return.
                List <ProjectionNode> derivedProjectionNodes = this.nodes.Where(n => !ResourceType.CompareReferences(n.TargetResourceType, this.ResourceType)).ToList();
                if (derivedProjectionNodes.Count > 0)
                {
                    foreach (ResourceType rt in provider.GetDerivedTypes(this.ResourceType))
                    {
                        if (derivedProjectionNodes.FirstOrDefault(node => node.TargetResourceType == rt) != null)
                        {
                            resourceTypes.Add(rt);
                        }
                    }
                }

#if DEBUG
                int count = this.nodes.Count;
#endif
                this.nodes = ExpandedProjectionNode.SortNodes(this.nodes, resourceTypes);
#if DEBUG
                Debug.Assert(this.nodes.Count == count, "We didn't sort all the properties.");
#endif
            }
        }