/// <summary> /// Verify that the property referred by the existing node and the new segment are both open properties or declared properties /// and if the existing node is an expand node, make sure that the target resource types are the same. /// </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> /// <param name="expandNode">true if the existingNode is an expand node.</param> private static void VerifyPropertyMismatchAndExpandSelectMismatchScenario(ProjectionNode existingNode, ResourceProperty property, ResourceType targetResourceType, bool expandNode) { Debug.Assert( existingNode.TargetResourceType.IsAssignableFrom(targetResourceType) || targetResourceType.IsAssignableFrom(existingNode.TargetResourceType), "This method must be called if the existingNode and targetResourceType are in the same inheritance chain"); Debug.Assert(!expandNode || existingNode is ExpandedProjectionNode, "If expandNode is true, then the existingNode must be an ExpandedProjectionNode"); if (property != existingNode.Property) { // If the property are not the same - it means one of them must be null. // This is only possible if super type is open and the property resolves to an open property. Debug.Assert(property == null || existingNode.Property == null, "One of the properties must be null, since the types belong to the same inheritance chain, and cannot have different property instance from a given property name"); // Currently we do not support scenarios where one refers to the open property on the supertype // and declared property on the sub type. throw DataServiceException.CreateBadRequestError( Strings.RequestQueryProcessor_CannotSpecifyOpenPropertyAndDeclaredPropertyAtTheSameTime( existingNode.PropertyName, property == null ? targetResourceType.FullName : existingNode.TargetResourceType.FullName, property == null ? existingNode.TargetResourceType.FullName : targetResourceType.FullName)); } if (!ResourceType.CompareReferences(targetResourceType, existingNode.TargetResourceType) && expandNode) { // If expand and select are specified on the same property within a given type // hierarchy, currently we enforce that they must be specified on the same type throw DataServiceException.CreateBadRequestError( Strings.RequestQueryProcessor_SelectAndExpandCannotBeSpecifiedTogether(existingNode.PropertyName)); } }
/// <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 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> /// Sort the list of existing projection nodes in the metadata order, first based on the type followed by the property order. /// </summary> /// <param name="existingNodes">existing projection nodes.</param> /// <param name="resourceTypesInMetadataOrder">list of resource types in metadata order.</param> /// <returns>the projection nodes in sorted manner.</returns> private static List <ProjectionNode> SortNodes(List <ProjectionNode> existingNodes, List <ResourceType> resourceTypesInMetadataOrder) { List <ProjectionNode> sortedNodes = new List <ProjectionNode>(existingNodes.Count); foreach (ResourceType resourceType in resourceTypesInMetadataOrder) { foreach (ResourceProperty property in resourceType.Properties) { // Since the same instance of resource property can be shared by 2 different resource types, // just comparing the resource property instances is not good enough. We also need to compare // the type references along with the property references. Debug.Assert( existingNodes.Where(node => node.Property == property && node.TargetResourceType == resourceType).Count() <= 1, "Can't have more than one projection segment for a given property."); ProjectionNode projectionNode = existingNodes.FirstOrDefault( node => node.Property == property && node.TargetResourceType == resourceType); if (projectionNode != null) { sortedNodes.Add(projectionNode); // We need to remove the node from the list, otherwise when we are going through more derived types, // we might encounter the same property again, since we are walking through all the properties on // a given resource type. existingNodes.Remove(projectionNode); } } } // And then append any open properties sorted alphabetically // We sort these since we don't want client to be able to influence // the server behavior unless aboslutely necessary. List <ProjectionNode> openPropertyProjectionNodes = existingNodes.Where(node => node.Property == null).ToList(); openPropertyProjectionNodes.Sort(new Comparison <ProjectionNode>((x, y) => { return(String.Compare(x.PropertyName, y.PropertyName, StringComparison.Ordinal)); })); sortedNodes.AddRange(openPropertyProjectionNodes); return(sortedNodes); }
/// <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> /// 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; }