/// <summary>Compares the defining type of this info and other EpmInfo object.</summary>
        /// <param name="other">The other EpmInfo object to compare to.</param>
        /// <returns>true if the defining types are the same</returns>
        internal bool DefiningTypesAreEqual(EntityPropertyMappingInfo other)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(other != null, "other != null");

            return(this.DefiningType == other.DefiningType);
        }
Example #2
0
        /// <summary>
        /// Validates conditional mapping.
        /// </summary>
        /// <param name="epmInfo">Epm mapping info</param>
        private static void ValidateConditionalMapping(EntityPropertyMappingInfo epmInfo)
        {
            Debug.Assert(epmInfo.CriteriaValue != null, "epmInfo.CriteriaValue != null");

            String criteriaValue = epmInfo.CriteriaValue;

            if (epmInfo.Criteria == EpmSyndicationCriteria.LinkRel && !EntityPropertyMappingInfo.IsValidLinkRelCriteriaValue(criteriaValue))
            {
                throw new ODataException(Strings.EpmSourceTree_ConditionalMappingInvalidLinkRelCriteriaValue(
                                             criteriaValue,
                                             epmInfo.Attribute.SourcePath,
                                             epmInfo.DefiningType.Name));
            }

            if (epmInfo.Criteria == EpmSyndicationCriteria.CategoryScheme && !EntityPropertyMappingInfo.IsValidCategorySchemeCriteriaValue(criteriaValue))
            {
                throw new ODataException(Strings.EpmSourceTree_ConditionalMappingInvalidCategorySchemeCriteriaValue(
                                             criteriaValue,
                                             epmInfo.Attribute.SourcePath,
                                             epmInfo.DefiningType.Name));
            }

            if (epmInfo.IsAtomLinkMapping)
            {
                if (epmInfo.Attribute.TargetSyndicationItem == SyndicationItemProperty.LinkRel)
                {
                    throw new ODataException(Strings.EpmTargetTree_ConditionalMappingToCriteriaAttribute(
                                                 epmInfo.Attribute.SourcePath,
                                                 epmInfo.DefiningType.Name,
                                                 epmInfo.Attribute.TargetPath));
                }
            }
            else if (epmInfo.IsAtomCategoryMapping)
            {
                if (epmInfo.Attribute.TargetSyndicationItem == SyndicationItemProperty.CategoryScheme)
                {
                    throw new ODataException(Strings.EpmTargetTree_ConditionalMappingToCriteriaAttribute(
                                                 epmInfo.Attribute.SourcePath,
                                                 epmInfo.DefiningType.Name,
                                                 epmInfo.Attribute.TargetPath));
                }
            }
            else
            {
                throw new ODataException(Strings.EpmTargetTree_ConditionalMappingToNonConditionalSyndicationItem(
                                             epmInfo.Attribute.SourcePath,
                                             epmInfo.DefiningType.Name,
                                             epmInfo.Attribute.TargetPath));
            }
        }
        /// <summary>
        /// Trims the start of the property value path by removing the path to the multivalue property.
        /// </summary>
        /// <param name="multiValueEpmInfo">Epm info for the multivalue property itself..</param>
        internal void TrimMultiValueItemPropertyPath(EntityPropertyMappingInfo multiValueEpmInfo)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(multiValueEpmInfo != null, "multiValueEpmInfo != null");
            Debug.Assert(multiValueEpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty, "The multiValueEpmInfo must be a multiValue property info.");
#if DEBUG
            Debug.Assert(multiValueEpmInfo.propertyValuePath.Length <= this.propertyValuePath.Length, "The prefix is longer than the actual source path.");
            for (int i = 0; i < multiValueEpmInfo.propertyValuePath.Length; i++)
            {
                Debug.Assert(multiValueEpmInfo.propertyValuePath[i] == this.propertyValuePath[i], "The prefix doesn't match the actual source path.");
            }
#endif

            this.propertyValuePath = this.propertyValuePath.Skip(multiValueEpmInfo.propertyValuePath.Length).ToArray();
        }
        /// <summary>
        /// Determines if the property with the specified value should be written into content or not.
        /// </summary>
        /// <param name="propertyValue">The property value to write.</param>
        /// <param name="epmSourcePathSegment">The EPM source path segment for the property being written.</param>
        /// <param name="version">The version of the protocol being used for the response.</param>
        /// <returns>true if the property should be written into content, or false otherwise</returns>
        private static bool ShouldWritePropertyInContent(object propertyValue, EpmSourcePathSegment epmSourcePathSegment, ODataVersion version)
        {
            if (epmSourcePathSegment == null)
            {
                return(true);
            }

            EntityPropertyMappingInfo epmInfo = epmSourcePathSegment.EpmInfo;

            if (epmInfo == null)
            {
                return(true);
            }

            EntityPropertyMappingAttribute epmAttribute = epmInfo.Attribute;

            Debug.Assert(epmAttribute != null, "Attribute should always be initialized for EpmInfo.");
            if (version <= ODataVersion.V2)
            {
                // In V2 and lower we sometimes write properties into content even if asked not to.
                // If the property value is null, we always write into content
                if (propertyValue == null)
                {
                    return(true);
                }

                string stringPropertyValue = propertyValue as string;
                if (stringPropertyValue != null && stringPropertyValue.Length == 0)
                {
                    // If the property value is an empty string and we should be writing it into an ATOM element which does not allow empty string
                    // we write it into content as well.
                    switch (epmAttribute.TargetSyndicationItem)
                    {
                    case SyndicationItemProperty.AuthorEmail:
                    case SyndicationItemProperty.AuthorUri:
                    case SyndicationItemProperty.ContributorEmail:
                    case SyndicationItemProperty.ContributorUri:
                        return(true);

                    default:
                        break;
                    }
                }
            }

            return(epmAttribute.KeepInContent);
        }
Example #5
0
        /// <summary>
        /// Given an <see cref="EntityPropertyMappingInfo"/> gives the correct target path for it
        /// </summary>
        /// <param name="epmInfo">Given <see cref="EntityPropertyMappingInfo"/></param>
        /// <returns>String with the correct value for the target path</returns>
        private static String GetPropertyNameFromEpmInfo(EntityPropertyMappingInfo epmInfo)
        {
            if (epmInfo.Attribute.TargetSyndicationItem == SyndicationItemProperty.CustomProperty)
            {
                return(epmInfo.Attribute.TargetPath);
            }
            else
            {
                // for EF provider we want to return a name that corresponds to attribute in the edmx file while for CLR provider
                // and the client we want to return a name that corresponds to the enum value used in EntityPropertyMapping attribute.
                return
                    (#if ASTORIA_SERVER
                     epmInfo.IsEFProvider ? EpmTranslate.MapSyndicationPropertyToEpmTargetPath(epmInfo.Attribute.TargetSyndicationItem) :
#endif
                     epmInfo.Attribute.TargetSyndicationItem.ToString());
            }
        }
Example #6
0
        internal void Add(EntityPropertyMappingInfo epmInfo)
        {
            DebugUtils.CheckNoExternalCallers();

            List <EpmSourcePathSegment> pathToCurrentSegment = new List <EpmSourcePathSegment>();
            EpmSourcePathSegment        currentSourceSegment = this.Root;
            EpmSourcePathSegment        foundSourceSegment   = null;
            ResourceType         currentType = epmInfo.ActualPropertyType;
            EpmSourcePathSegment multiValuePropertySegment = null;

            Debug.Assert(!string.IsNullOrEmpty(epmInfo.Attribute.SourcePath), "Invalid source path");
            string[] propertyPath = epmInfo.Attribute.SourcePath.Split('/');

            if (epmInfo.CriteriaValue != null)
            {
                ValidateConditionalMapping(epmInfo);
            }

            Debug.Assert(propertyPath.Length > 0, "Must have been validated during EntityPropertyMappingAttribute construction");
            for (int sourcePropertyIndex = 0; sourcePropertyIndex < propertyPath.Length; sourcePropertyIndex++)
            {
                string propertyName = propertyPath[sourcePropertyIndex];

                if (propertyName.Length == 0)
                {
                    throw new ODataException(Strings.EpmSourceTree_InvalidSourcePath(epmInfo.DefiningType.Name, epmInfo.Attribute.SourcePath));
                }

                bool isMultiValueProperty;
                currentType = GetPropertyType(currentType, propertyName, out isMultiValueProperty);

                foundSourceSegment = currentSourceSegment.SubProperties.SingleOrDefault(e => e.PropertyName == propertyName);
                if (foundSourceSegment != null)
                {
                    currentSourceSegment = foundSourceSegment;
                }
                else
                {
                    EpmSourcePathSegment newSourceSegment = new EpmSourcePathSegment(propertyName);
                    currentSourceSegment.SubProperties.Add(newSourceSegment);
                    currentSourceSegment = newSourceSegment;
                }

                pathToCurrentSegment.Add(currentSourceSegment);

                if (isMultiValueProperty)
                {
                    Debug.Assert(
                        currentSourceSegment.EpmInfo == null || currentSourceSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty,
                        "MultiValue property must have EpmInfo marked as MultiValue or none at all.");
                    Debug.Assert(
                        currentSourceSegment.EpmInfo != null || foundSourceSegment == null,
                        "The only way to get a propety without info attached yet on a MultiValue property is when we just created it.");

                    if (multiValuePropertySegment != null)
                    {
                        // Nested MultiValue - not allowed to be mapped
                        throw new ODataException(Strings.EpmSourceTree_NestedMultiValue(
                                                     multiValuePropertySegment.EpmInfo.Attribute.SourcePath,
                                                     multiValuePropertySegment.EpmInfo.DefiningType.Name,
                                                     epmInfo.Attribute.SourcePath));
                    }

                    multiValuePropertySegment = currentSourceSegment;

                    // MultiValue properties can only be mapped to a top-level element, so we can blindly use the first part
                    //   of the target path as the target path for the MultiValue property.
                    Debug.Assert(!string.IsNullOrEmpty(epmInfo.Attribute.TargetPath), "Target path should have been checked by the EpmAttribute constructor.");
                    string multiValuePropertyTargetPath = epmInfo.Attribute.TargetPath.Split('/')[0];

                    if (currentSourceSegment.EpmInfo == null || !currentSourceSegment.EpmInfo.DefiningTypesAreEqual(epmInfo))
                    {
                        if (currentSourceSegment.EpmInfo != null)
                        {
                            Debug.Assert(!currentSourceSegment.EpmInfo.DefiningTypesAreEqual(epmInfo), "Just verifying that the ifs are correct.");
                            Debug.Assert(foundSourceSegment != null, "Can't have existing node with EpmInfo on it here and not found it before.");

                            // If the MultiValue property we're trying to add is from a different type than the one we already have
                            //   just overwrite the epm info. This means that the derived type defines a different mapping for the property than the base type.
                            //   We also need to walk all the children of the base type mapping here and remove them from the target tree
                            //   since we're overriding the entire MultiValue property mapping (not just one item property)
                            // Note that for MultiValue properties, removing the MultiValue property node itself will remove all the MultiValue item properties as well
                            //   as they have to be children of the MultiValue property node in the target tree.
                            this.epmTargetTree.Remove(foundSourceSegment.EpmInfo);

                            // We also need to remove all children of the MultiValue property node from the source tree
                            //   as the derived type is overriding it completely. If the derived type doesn't override all of the properties
                            //   we should fail as if it did't map all of them.
                            currentSourceSegment.SubProperties.Clear();
                        }

                        // This is the first time we've seen this MultiValue property mapped
                        //   (on this type, we might have seen it on the base type, but that has been removed)

                        // The source path is the path we have so far for the property
                        string multiValuePropertySourcePath = string.Join("/", propertyPath, 0, sourcePropertyIndex + 1);

                        if (!epmInfo.IsSyndicationMapping)
                        {
                            // Custom EPM for MultiValue is not supported yet
                            // Note: This has already been implemented, but then removed from the code. To see what it takes to implement this
                            //   please see the change which adds this comment into the sources.
                            throw new ODataException(Strings.EpmSourceTree_MultiValueNotAllowedInCustomMapping(
                                                         multiValuePropertySourcePath,
                                                         epmInfo.DefiningType.Name));
                        }

                        // Create a new EPM attribute to represent the MultiValue property mapping
                        //   note that this attribute is basically implicitly declared whenever the user declares EPM attribute
                        //   for a property from some MultiValue property. (the declaration happens right here)
                        EntityPropertyMappingAttribute multiValueEpmAttribute = new EntityPropertyMappingAttribute(
                            multiValuePropertySourcePath,
                            multiValuePropertyTargetPath,
                            epmInfo.Attribute.TargetNamespacePrefix,
                            epmInfo.Attribute.TargetNamespaceUri,
                            epmInfo.Attribute.KeepInContent);

                        // Create a special EpmInfo from the above special attribute which represents just the MultiValue property itself
                        EntityPropertyMappingInfo multiValueEpmInfo = new EntityPropertyMappingInfo(
                            multiValueEpmAttribute,
                            epmInfo.DefiningType,
                            epmInfo.ActualPropertyType);
                        multiValueEpmInfo.MultiValueStatus   = EntityPropertyMappingMultiValueStatus.MultiValueProperty;
                        multiValueEpmInfo.MultiValueItemType = currentType;

                        // We need to mark the info as syndication/custom mapping explicitely since the attribute we create (From which it's infered) is always custom mapping
                        Debug.Assert(epmInfo.IsSyndicationMapping, "Only syndication mapping is allowed for MultiValue properties.");
                        multiValueEpmInfo.SetMultiValuePropertySyndicationMapping();

                        multiValueEpmInfo.SetPropertyValuePath(pathToCurrentSegment.ToArray());

                        multiValueEpmInfo.Criteria      = epmInfo.Criteria;
                        multiValueEpmInfo.CriteriaValue = epmInfo.CriteriaValue;

                        // Now associate the current source tree segment with the new info object (or override the one from base)
                        currentSourceSegment.EpmInfo = multiValueEpmInfo;

                        // And add the new info to the target tree
                        this.epmTargetTree.Add(multiValueEpmInfo);

                        // And continue with the walk of the source path.
                        // This means that the EPM attribute specified as the input to this method is still to be added
                        // It might be added to the source tree if the path is longer (property on an item in the MultiValue property of complex types)
                        //   or it might not be added to the source tree if this segment is the last (MultiValue property of primitive types).
                        // In any case it will be added to the target tree (so even if the MultiValue property itself is mapped to the top-level element only
                        //   the items in the MultiValue property can be mapped to child element/attribute of that top-level element).
                    }
                    else
                    {
                        Debug.Assert(currentSourceSegment.EpmInfo.DefiningTypesAreEqual(epmInfo), "The condition in the surrounding if is broken.");

                        // We have already found a MultiValue property mapped from this source node.
                        // If it's on the same defining type we need to make sure that it's the same MultiValue property being mapped
                        // First verify that the mapping for the other property has the same top-level element for the MultiValue property
                        //   since we only allow properties from one MultiValue property to be mapped to the same top-level element
                        if (multiValuePropertyTargetPath != currentSourceSegment.EpmInfo.Attribute.TargetPath ||
                            epmInfo.Attribute.TargetNamespacePrefix != currentSourceSegment.EpmInfo.Attribute.TargetNamespacePrefix ||
                            epmInfo.Attribute.TargetNamespaceUri != currentSourceSegment.EpmInfo.Attribute.TargetNamespaceUri ||
                            epmInfo.Criteria != currentSourceSegment.EpmInfo.Criteria ||
                            String.Compare(epmInfo.Attribute.CriteriaValue, currentSourceSegment.EpmInfo.CriteriaValue, StringComparison.OrdinalIgnoreCase) != 0)
                        {
                            throw new ODataException(Strings.EpmSourceTree_PropertiesFromSameMultiValueMappedToDifferentTopLevelElements(currentSourceSegment.EpmInfo.Attribute.SourcePath, currentSourceSegment.EpmInfo.DefiningType.Name));
                        }

                        // Second verify that the mappings for both properties have the same KeepInContent value
                        if (epmInfo.Attribute.KeepInContent != currentSourceSegment.EpmInfo.Attribute.KeepInContent)
                        {
                            throw new ODataException(Strings.EpmSourceTree_PropertiesFromSameMultiValueMappedWithDifferentKeepInContent(currentSourceSegment.EpmInfo.Attribute.SourcePath, currentSourceSegment.EpmInfo.DefiningType.Name));
                        }
                    }
                }
            }

            // The last segment is the one being mapped from by the user specified attribute.
            // It must be a primitive type - we don't allow mappings of anything else than primitive properties directly.
            // Note that we can only verify this for non-open properties, for open properties we must assume it's a primitive type
            //   and we will make this check later during serialization again when we actually have the value of the property.
            if (currentType != null)
            {
                if (currentType.ResourceTypeKind != ResourceTypeKind.Primitive)
                {
                    throw new ODataException(Strings.EpmSourceTree_EndsWithNonPrimitiveType(currentSourceSegment.PropertyName));
                }

                SyndicationItemProperty targetSyndicationItem = epmInfo.Attribute.TargetSyndicationItem;
                if (targetSyndicationItem == SyndicationItemProperty.LinkRel || targetSyndicationItem == SyndicationItemProperty.CategoryScheme)
                {
                    if (PrimitiveStringResourceType != currentType)
                    {
                        throw new InvalidOperationException(Strings.EpmSourceTree_NonStringPropertyMappedToConditionAttribute(
                                                                currentSourceSegment.PropertyName,
                                                                epmInfo.DefiningType.FullName,
                                                                targetSyndicationItem.ToString()));
                    }
                }
            }

            if (multiValuePropertySegment == currentSourceSegment)
            {
                // If the MultiValue property is the last segment it means that the MultiValue property itself is being mapped (and it must be a MultiValue of primitive types).

                // If we found the MultiValue property already in the tree, here we actually want the item of the MultiValue property (as the MultiValue one was processed above already)
                // If we have the item value already in the tree use it as the foundProperty so that we correctly check the duplicate mappings below
                if (foundSourceSegment != null)
                {
                    Debug.Assert(foundSourceSegment == currentSourceSegment, "If we found an existing segment it must be the current one.");
                    foundSourceSegment = currentSourceSegment.SubProperties.SingleOrDefault(e => e.IsMultiValueItemValue);
                }

                if (foundSourceSegment == null)
                {
                    // This is a bit of a special case. In the source tree we will create a special node to represent the item value (we need that to be able to tell
                    //   if it was not mapped twice).
                    // In the target tree, we will also create a special node which will hold the information specific
                    //   to serialization of the item value (for example the exact syndication mapping target and so on).
                    //   The creation of the special node is done in the target tree Add method.
                    EpmSourcePathSegment newSegment = EpmSourcePathSegment.CreateMultiValueItemValueSegment();
                    currentSourceSegment.SubProperties.Add(newSegment);
                    currentSourceSegment = newSegment;
                }
                else
                {
                    currentSourceSegment = foundSourceSegment;
                }
            }

            // Note that once we're here the EpmInfo we have is never the MultiValue property itself, it's always either a non-MultiValue property
            //   or MultiValue item property.
            Debug.Assert(foundSourceSegment == null || foundSourceSegment.EpmInfo != null, "Can't have a leaf node in the tree without EpmInfo.");

            // Two EpmAttributes with same PropertyName in the same ResourceType, this could be a result of inheritance
            if (foundSourceSegment != null)
            {
                Debug.Assert(Object.ReferenceEquals(foundSourceSegment, currentSourceSegment), "currentSourceSegment variable should have been updated already to foundSourceSegment");
                Debug.Assert(
                    foundSourceSegment.EpmInfo.MultiValueStatus != EntityPropertyMappingMultiValueStatus.MultiValueProperty,
                    "We should never get here with a MultiValue property itself, we should have a node represent its item or property on the item instead.");

                // Check for duplicates on the same entity type
                Debug.Assert(foundSourceSegment.SubProperties.Count == 0, "If non-leaf, it means we allowed complex type to be a leaf node");
                if (foundSourceSegment.EpmInfo.DefiningTypesAreEqual(epmInfo))
                {
                    throw new ODataException(Strings.EpmSourceTree_DuplicateEpmAttrsWithSameSourceName(epmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name));
                }

                // In case of inheritance, we need to remove the node from target tree which was mapped to base type property
                this.epmTargetTree.Remove(foundSourceSegment.EpmInfo);
            }

            epmInfo.SetPropertyValuePath(pathToCurrentSegment.ToArray());
            currentSourceSegment.EpmInfo = epmInfo;

            if (multiValuePropertySegment != null)
            {
                Debug.Assert(multiValuePropertySegment.EpmInfo != null, "All MultiValue property segments must have EpmInfo assigned.");

                // We are mapping a MultiValue property - so mark the info as a MultiValue item property (since the MultiValue property itself was added above)
                epmInfo.MultiValueStatus = EntityPropertyMappingMultiValueStatus.MultiValueItemProperty;

                // Set the item type on each of the item properties, so that the segmented path know from which type to start
                epmInfo.MultiValueItemType = multiValuePropertySegment.EpmInfo.MultiValueItemType;

                // And trim its property value path to start from the MultiValue item. This path is basically a list of properties to traverse
                //   when access the value of the property on the specified resource. For non-MultiValue and MultiValue properties themselves
                //   this path starts with the entity instance. For MultiValue item properties this path starts with the MultiValue item instance.
                //   Note that if it's a MultiValue of primitive types, the path is going to be empty meaning that the value is the item instance itself.
                epmInfo.TrimMultiValueItemPropertyPath(multiValuePropertySegment.EpmInfo);

#if DEBUG
                // Check that if the MultiValue item is of primitive type, we can only ever add a single child source segment which points directly to the MultiValue property itself
                // If we would allow this here, we would fail later, but with a much weirder error message
                Debug.Assert(
                    multiValuePropertySegment.EpmInfo.MultiValueItemType.ResourceTypeKind != ResourceTypeKind.Primitive || epmInfo.PropertyValuePath.Length == 0,
                    "We shoud have failed to map a subproperty of a primitive MultiValue item.");
#endif
            }

            this.epmTargetTree.Add(epmInfo);
        }
Example #7
0
        /// <summary>
        /// Validates the specified segment which is a subsegment of a MultiValue property or the MultiValue property segment itself.
        /// </summary>
        /// <param name="multiValuePropertyInfo">Info about the MultiValue property being processed. Used for exception messages only.</param>
        /// <param name="multiValueSegment">The segment belonging to a MultiValue property to validate.</param>
        /// <param name="resourceType">The resource type of the property represented by this segment (item type for the MultiValue property itself).</param>
        private static void ValidateMultiValueSegment(
            EntityPropertyMappingInfo multiValuePropertyInfo,
            EpmSourcePathSegment multiValueSegment,
            ResourceType resourceType)
        {
            Debug.Assert(
                multiValuePropertyInfo != null && multiValuePropertyInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty,
                "multiValuePropertyName must be non-null and must be a MultiValue info.");
            Debug.Assert(multiValueSegment != null, "multiValueSegment != null");
            Debug.Assert(resourceType != null, "resourceType != null");
            Debug.Assert(
                multiValueSegment.EpmInfo == null ||
                multiValueSegment.EpmInfo == multiValuePropertyInfo ||
                multiValueSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueItemProperty,
                "The specified segment does not belong to a MultiValue property subtree.");

            if (resourceType.ResourceTypeKind == ResourceTypeKind.ComplexType)
            {
                Debug.Assert(
                    multiValueSegment.EpmInfo == null || multiValueSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty,
                    "EPM source segment representing a complex property of a MultiValue property must not have an EpmInfo for the property itself.");

                // Verify that all properties of the complex type are mapped (have respective source segments)
                foreach (ResourceProperty property in resourceType.Properties)
                {
                    string propertyName             = property.Name;
                    string resourceTypeName         = resourceType.Name;
                    EpmSourcePathSegment subSegment = multiValueSegment.SubProperties.SingleOrDefault(e => e.PropertyName == propertyName);
                    if (subSegment == null)
                    {
                        throw new ODataException(Strings.EpmSourceTree_NotAllMultiValueItemPropertiesMapped(
                                                     multiValuePropertyInfo.Attribute.SourcePath,
                                                     multiValuePropertyInfo.DefiningType.Name,
                                                     propertyName,
                                                     resourceTypeName));
                    }
                    else
                    {
                        ResourceType propertyType = property.ResourceType;

                        // Recursive call to verify the sub segment
                        // Note that we don't need to check for enless loops and recursion depth because we are effectively walking the EPM source tree
                        //   which itself can't have loops in it, and can't be infinite either. So if the metadata for a MultiValue property has loops in it
                        //   we would eventually fail to find a matching segment in the source tree and throw.
                        ValidateMultiValueSegment(multiValuePropertyInfo, subSegment, propertyType);
                    }
                }
            }
            else
            {
                Debug.Assert(multiValueSegment.EpmInfo != null, "Primitive value must have EpmInfo.");

                if (multiValueSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty)
                {
                    // MultiValue of primitive types
                    Debug.Assert(multiValueSegment.SubProperties.Count == 1, "Exactly one subproperty should be on a node representing a MultiValue property of primitive types.");
                    Debug.Assert(
                        multiValueSegment.SubProperties[0].IsMultiValueItemValue && multiValueSegment.SubProperties[0].EpmInfo != null &&
                        multiValueSegment.SubProperties[0].EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueItemProperty,
                        "The only subproperty of a collectin of primitive types should be the special item's value node.");
                    EpmSourcePathSegment leafSegment = multiValueSegment.SubProperties[0];
                    if (leafSegment.EpmInfo.IsAtomLinkMapping)
                    {
                        if (leafSegment.EpmInfo.CriteriaValue == null)
                        {
                            throw new ODataException(Strings.EpmSourceTree_MultiValueOfPrimitiveMappedToLinkWithoutCriteria(
                                                         multiValuePropertyInfo.Attribute.SourcePath,
                                                         multiValuePropertyInfo.DefiningType.Name,
                                                         leafSegment.EpmInfo.Attribute.TargetPath));
                        }
                        else if (leafSegment.EpmInfo.Attribute.TargetSyndicationItem != SyndicationItemProperty.LinkHref)
                        {
                            throw new ODataException(Strings.EpmTargetTree_ConditionalMappingLinkHrefIsRequired(
                                                         multiValuePropertyInfo.Attribute.SourcePath,
                                                         multiValuePropertyInfo.DefiningType.Name,
                                                         leafSegment.EpmInfo.Attribute.TargetPath));
                        }
                    }
                    else if (leafSegment.EpmInfo.IsAtomCategoryMapping)
                    {
                        if (leafSegment.EpmInfo.CriteriaValue == null && leafSegment.EpmInfo.Attribute.TargetSyndicationItem != SyndicationItemProperty.CategoryTerm)
                        {
                            throw new ODataException(Strings.EpmTargetTree_ConditionalMappingCategoryTermIsRequired(
                                                         multiValuePropertyInfo.Attribute.SourcePath,
                                                         multiValuePropertyInfo.DefiningType.Name,
                                                         leafSegment.EpmInfo.Attribute.TargetPath));
                        }
                    }
                }
                else
                {
                    // MultiValue of complex types, we're on a leaf primitive property.
                    Debug.Assert(
                        multiValueSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueItemProperty,
                        "Only multiValue property or multiValue item property nodes should get here.");
                    Debug.Assert(multiValueSegment.SubProperties.Count == 0, "Primtive item property value should have no sub properties.");
                    Debug.Assert(!multiValueSegment.IsMultiValueItemValue, "Special MultiValue item value must not be a child of a complex property.");

                    EntityPropertyMappingInfo multiValueSegmentEpmInfo = multiValueSegment.EpmInfo;
                    if (multiValueSegmentEpmInfo.IsAtomLinkMapping != multiValueSegmentEpmInfo.IsAtomLinkMapping ||
                        multiValueSegmentEpmInfo.IsAtomCategoryMapping != multiValueSegmentEpmInfo.IsAtomCategoryMapping ||
                        String.Compare(multiValueSegmentEpmInfo.CriteriaValue, multiValueSegmentEpmInfo.CriteriaValue, StringComparison.OrdinalIgnoreCase) != 0)
                    {
                        throw new ODataException(Strings.EpmSourceTree_MultiValueOfComplexTypesDifferentConditionalMapping(
                                                     multiValuePropertyInfo.Attribute.SourcePath,
                                                     multiValuePropertyInfo.DefiningType.Name,
                                                     multiValuePropertyInfo.Attribute.TargetPath));
                    }
                }
            }
        }
Example #8
0
        /// <summary>
        /// Writes the syndication part of EPM for an entry into ATOM metadata OM.
        /// </summary>
        /// <param name="epmTargetTree">The EPM target tree to use.</param>
        /// <param name="epmValueCache">The entry properties value cache to use to access the properties.</param>
        /// <param name="resourceType">The resource type of the entry.</param>
        /// <param name="metadata">The metadata provider to use.</param>
        /// <param name="version">The version of OData protocol to use.</param>
        /// <returns>The ATOM metadata OM with the EPM values populated.</returns>
        internal static AtomEntryMetadata WriteEntryEpm(
            EpmTargetTree epmTargetTree,
            EntryPropertiesValueCache epmValueCache,
            ResourceType resourceType,
            DataServiceMetadataProviderWrapper metadata,
            ODataVersion version)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(epmTargetTree != null, "epmTargetTree != null");
            Debug.Assert(epmValueCache != null, "epmValueCache != null");
            Debug.Assert(resourceType != null, "For any EPM to exist the metadata must be available.");

            // If there are no syndication mappings, just return null.
            EpmTargetPathSegment syndicationRootSegment = epmTargetTree.SyndicationRoot;

            Debug.Assert(syndicationRootSegment != null, "EPM Target tree must always have syndication root.");
            if (syndicationRootSegment.SubSegments.Count == 0)
            {
                return(null);
            }

            AtomEntryMetadata entryMetadata = new AtomEntryMetadata();

            foreach (EpmTargetPathSegment targetSegment in syndicationRootSegment.SubSegments)
            {
                if (targetSegment.IsMultiValueProperty)
                {
                    Debug.Assert(
                        targetSegment.EpmInfo != null && targetSegment.EpmInfo.Attribute != null,
                        "MultiValue property target segment must have EpmInfo and the Epm Attribute.");

                    ODataVersionChecker.CheckMultiValueProperties(version, targetSegment.EpmInfo.Attribute.SourcePath);

                    // WriteMultiValueEpm(entry, targetSegment, epmValueCache);
                    throw new NotImplementedException();
                }
                else if (targetSegment.HasContent)
                {
                    EntityPropertyMappingInfo epmInfo = targetSegment.EpmInfo;
                    Debug.Assert(
                        epmInfo != null && epmInfo.Attribute != null,
                        "If the segment has content it must have EpmInfo which in turn must have the Epm attribute");

                    bool   nullOnParentProperty;
                    object propertyValue     = epmInfo.ReadEntryPropertyValue(epmValueCache, resourceType, metadata, out nullOnParentProperty);
                    string textPropertyValue = EpmWriterUtils.GetPropertyValueAsText(propertyValue);

                    switch (epmInfo.Attribute.TargetSyndicationItem)
                    {
                    case SyndicationItemProperty.Updated:
                        entryMetadata.Updated = CreateDateTimeValue(propertyValue, SyndicationItemProperty.Updated, version);
                        break;

                    case SyndicationItemProperty.Published:
                        entryMetadata.Published = CreateDateTimeValue(propertyValue, SyndicationItemProperty.Published, version);
                        break;

                    case SyndicationItemProperty.Rights:
                        entryMetadata.Rights = CreateAtomTextConstruct(textPropertyValue, epmInfo.Attribute.TargetTextContentKind, version);
                        break;

                    case SyndicationItemProperty.Summary:
                        entryMetadata.Summary = CreateAtomTextConstruct(textPropertyValue, epmInfo.Attribute.TargetTextContentKind, version);
                        break;

                    case SyndicationItemProperty.Title:
                        entryMetadata.Title = CreateAtomTextConstruct(textPropertyValue, epmInfo.Attribute.TargetTextContentKind, version);
                        break;

                    default:
                        throw new ODataException(Strings.General_InternalError(InternalErrorCodes.EpmSyndicationWriter_WriteEntryEpm_ContentTarget));
                    }
                }
                else if (targetSegment.SegmentName == AtomConstants.AtomAuthorElementName)
                {
                    AtomPersonMetadata authorMetadata = WritePersonEpm(targetSegment, epmValueCache, resourceType, metadata);

                    Debug.Assert(entryMetadata.Authors == null, "Found two mappings to author, that is invalid.");
                    if (authorMetadata != null)
                    {
                        entryMetadata.Authors = CreateSinglePersonList(authorMetadata);
                    }
                }
                else if (targetSegment.SegmentName == AtomConstants.AtomContributorElementName)
                {
                    AtomPersonMetadata contributorMetadata = WritePersonEpm(targetSegment, epmValueCache, resourceType, metadata);

                    Debug.Assert(entryMetadata.Contributors == null, "Found two mappings to contributor, that is invalid.");
                    if (contributorMetadata != null)
                    {
                        entryMetadata.Contributors = CreateSinglePersonList(contributorMetadata);
                    }
                }
                else if (targetSegment.SegmentName == AtomConstants.AtomLinkElementName)
                {
                    AtomLinkMetadata linkMetadata = new AtomLinkMetadata();
                    //// WriteLinkEpm(entry, targetSegment, epmValueCache);

                    Debug.Assert(targetSegment.CriteriaValue != null, "Mapping to link must be conditional.");
                    linkMetadata.Relation = targetSegment.CriteriaValue;
                    List <AtomLinkMetadata> links;
                    if (entryMetadata.Links == null)
                    {
                        links = new List <AtomLinkMetadata>();
                        entryMetadata.Links = links;
                    }
                    else
                    {
                        links = entryMetadata.Links as List <AtomLinkMetadata>;
                        Debug.Assert(links != null, "AtomEntryMetadata.Links must be of type List<AtomLinkMetadata> since we create it like that.");
                    }

                    links.Add(linkMetadata);

                    throw new NotImplementedException();
                }
                else if (targetSegment.SegmentName == AtomConstants.AtomCategoryElementName)
                {
                    AtomCategoryMetadata categoryMetadata = new AtomCategoryMetadata();
                    //// WriteCategoryEpm(entry, targetSegment, epmValueCache)

                    Debug.Assert(targetSegment.CriteriaValue != null, "Mapping to category must be conditional.");
                    categoryMetadata.Scheme = targetSegment.CriteriaValue;
                    List <AtomCategoryMetadata> categories;
                    if (entryMetadata.Categories == null)
                    {
                        categories = new List <AtomCategoryMetadata>();
                        entryMetadata.Categories = categories;
                    }
                    else
                    {
                        categories = entryMetadata.Links as List <AtomCategoryMetadata>;
                        Debug.Assert(categories != null, "AtomEntryMetadata.Categories must be of type List<AtomCategoryMetadata> since we create it like that.");
                    }

                    categories.Add(categoryMetadata);

                    throw new NotImplementedException();
                }
                else
                {
                    throw new ODataException(Strings.General_InternalError(InternalErrorCodes.EpmSyndicationWriter_WriteEntryEpm_TargetSegment));
                }
            }

            return(entryMetadata);
        }
Example #9
0
        /// <summary>
        /// Removes a path in the tree which is obtained by looking at the EntityPropertyMappingAttribute in the <paramref name="epmInfo"/>.
        /// </summary>
        /// <param name="epmInfo">EnitityPropertyMappingInfo holding the target path</param>
        internal void Remove(EntityPropertyMappingInfo epmInfo)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(epmInfo != null, "epmInfo != null");

            // We should never try to remove a multiValue item property from the target tree.
            // If derived type redefines mapping for a given multiValue, the multiValue node itself should be removed first and then all its new items
            //   should be added (but since the multiValue node, that is their parent, was replaces, there should be no collisions and thus no need to remove anything)
            Debug.Assert(epmInfo.MultiValueStatus != EntityPropertyMappingMultiValueStatus.MultiValueItemProperty, "We should never try to remove a multiValue item property.");

            String targetName   = epmInfo.Attribute.TargetPath;
            String namespaceUri = epmInfo.Attribute.TargetNamespaceUri;

            EpmTargetPathSegment        currentSegment    = epmInfo.IsSyndicationMapping ? this.SyndicationRoot : this.NonSyndicationRoot;
            List <EpmTargetPathSegment> activeSubSegments = currentSegment.SubSegments;

            Debug.Assert(!String.IsNullOrEmpty(targetName), "Must have been validated during EntityPropertyMappingAttribute construction");
            String[] targetSegments = targetName.Split('/');
            for (int i = 0; i < targetSegments.Length; i++)
            {
                String targetSegment = targetSegments[i];

                Debug.Assert(targetSegment.Length > 0 && (targetSegment[0] != '@' || i == targetSegments.Length - 1), "Target segments should have been checked when adding the path to the tree");

                EpmTargetPathSegment foundSegment = activeSubSegments.FirstOrDefault(
                    segment => segment.SegmentName == targetSegment &&
                    (epmInfo.IsSyndicationMapping || segment.SegmentNamespaceUri == namespaceUri) &&
                    segment.MatchCriteria(epmInfo.CriteriaValue, epmInfo.Criteria));
                if (foundSegment != null)
                {
                    currentSegment = foundSegment;
                }
                else
                {
                    return;
                }

                activeSubSegments = currentSegment.SubSegments;
            }

            // Recursively remove all the parent segments which will have no more children left
            // after removal of the current segment node
            if (currentSegment.EpmInfo != null)
            {
                // Since we are removing a property with KeepInContent false, we should decrement the count
                if (!currentSegment.EpmInfo.Attribute.KeepInContent)
                {
                    if (currentSegment.EpmInfo.IsAtomLinkMapping || currentSegment.EpmInfo.IsAtomCategoryMapping)
                    {
                        this.countOfNonContentV3mappings--;
                    }
                    else
                    {
                        this.countOfNonContentV2mappings--;
                    }
                }

                EpmTargetPathSegment parentSegment = null;
                do
                {
                    // We should never be removing the multiValue property due to its children being removed
                    Debug.Assert(
                        currentSegment.EpmInfo == null || currentSegment.EpmInfo.MultiValueStatus != EntityPropertyMappingMultiValueStatus.MultiValueProperty || parentSegment == null,
                        "We should never be removing the multiValue property due to its child being removed. The source tree Add method should remove the multiValue property node itself first in that case.");

                    parentSegment = currentSegment.ParentSegment;
                    parentSegment.SubSegments.Remove(currentSegment);
                    currentSegment = parentSegment;
                }while (currentSegment.ParentSegment != null && !currentSegment.HasContent && currentSegment.SubSegments.Count == 0);
            }
        }
Example #10
0
        internal void Add(EntityPropertyMappingInfo epmInfo)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(epmInfo != null, "epmInfo != null");

            String targetPath      = epmInfo.Attribute.TargetPath;
            String namespaceUri    = epmInfo.Attribute.TargetNamespaceUri;
            String namespacePrefix = epmInfo.Attribute.TargetNamespacePrefix;

            EpmTargetPathSegment         currentSegment    = epmInfo.IsSyndicationMapping ? this.SyndicationRoot : this.NonSyndicationRoot;
            IList <EpmTargetPathSegment> activeSubSegments = currentSegment.SubSegments;

            Debug.Assert(!String.IsNullOrEmpty(targetPath), "Must have been validated during EntityPropertyMappingAttribute construction");
            String[] targetSegments = targetPath.Split('/');

            EpmTargetPathSegment foundSegment      = null;
            EpmTargetPathSegment multiValueSegment = null;

            for (int i = 0; i < targetSegments.Length; i++)
            {
                String targetSegment = targetSegments[i];

                if (targetSegment.Length == 0)
                {
                    throw new ODataException(Strings.EpmTargetTree_InvalidTargetPath(targetPath));
                }

                if (targetSegment[0] == '@' && i != targetSegments.Length - 1)
                {
                    throw new ODataException(Strings.EpmTargetTree_AttributeInMiddle(targetSegment));
                }

                foundSegment = activeSubSegments.SingleOrDefault(
                    segment => segment.SegmentName == targetSegment &&
                    (epmInfo.IsSyndicationMapping || segment.SegmentNamespaceUri == namespaceUri) &&
                    segment.MatchCriteria(epmInfo.CriteriaValue, epmInfo.Criteria));

                if (foundSegment != null)
                {
                    currentSegment = foundSegment;
                    if (currentSegment.EpmInfo != null && currentSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty)
                    {
                        multiValueSegment = currentSegment;
                    }
                }
                else
                {
                    currentSegment               = new EpmTargetPathSegment(targetSegment, namespaceUri, namespacePrefix, currentSegment);
                    currentSegment.Criteria      = epmInfo.Criteria;
                    currentSegment.CriteriaValue = epmInfo.CriteriaValue;

                    if (targetSegment[0] == '@')
                    {
                        activeSubSegments.Insert(0, currentSegment);
                    }
                    else
                    {
                        activeSubSegments.Add(currentSegment);
                    }
                }

                activeSubSegments = currentSegment.SubSegments;
            }

            // If we're adding a multiValue property to already existing segment which maps to a non-multiValue property (no EpmInfo or one pointing to a non-multiValue property)
            // OR if we're adding a non-multiValue property to a segment which has multiValue in its path
            //   we need to fail, since it's invalid to have multiValue property being mapped to the same top-level element as anything else.
            if ((epmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty &&
                 foundSegment != null &&
                 (foundSegment.EpmInfo == null || foundSegment.EpmInfo.MultiValueStatus != EntityPropertyMappingMultiValueStatus.MultiValueProperty)) ||
                (epmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.None &&
                 multiValueSegment != null))
            {
                EntityPropertyMappingInfo multiValuePropertyEpmInfo = epmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty ?
                                                                      epmInfo : multiValueSegment.EpmInfo;

                // Trying to map MultiValue property to the same target as something else which was already mapped there
                // It is ok to map to atom:category and atom:link elements with different sources only if the criteria values are different.
                if (epmInfo.CriteriaValue != null)
                {
                    throw new ODataException(Strings.EpmTargetTree_MultiValueAndNormalPropertyMappedToTheSameConditionalTopLevelElement(
                                                 multiValuePropertyEpmInfo.Attribute.SourcePath,
                                                 epmInfo.DefiningType.Name,
                                                 EpmTargetTree.GetPropertyNameFromEpmInfo(multiValuePropertyEpmInfo),
                                                 epmInfo.CriteriaValue));
                }
                else
                {
                    throw new ODataException(Strings.EpmTargetTree_MultiValueAndNormalPropertyMappedToTheSameTopLevelElement(
                                                 multiValuePropertyEpmInfo.Attribute.SourcePath,
                                                 epmInfo.DefiningType.Name,
                                                 EpmTargetTree.GetPropertyNameFromEpmInfo(multiValuePropertyEpmInfo)));
                }
            }

            Debug.Assert(
                epmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.None || epmInfo.IsSyndicationMapping,
                "Custom EPM mapping is not supported for multiValue properties.");

            // We only allow multiValues to map to ATOM constructs which can be repeated.
            if (epmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueItemProperty)
            {
                Debug.Assert(
                    epmInfo.Attribute.TargetSyndicationItem != SyndicationItemProperty.CustomProperty,
                    "Trying to add custom mapped property to a syndication target tree.");

                // Right now all the SyndicationItemProperty targets which do not have SyndicationParent.Entry are valid targets for multiValues
                // and all the ones which have SyndicationParent.Entry (Title, Updated etc.) are not valid targets for multiValues.
                if (epmInfo.SyndicationParent == EpmSyndicationParent.Entry)
                {
                    throw new ODataException(Strings.EpmTargetTree_MultiValueMappedToNonRepeatableAtomElement(
                                                 epmInfo.Attribute.SourcePath,
                                                 epmInfo.DefiningType.Name,
                                                 EpmTargetTree.GetPropertyNameFromEpmInfo(epmInfo)));
                }
            }

            Debug.Assert(
                epmInfo.MultiValueStatus != EntityPropertyMappingMultiValueStatus.MultiValueProperty || targetSegments.Length == 1,
                "MultiValue property itself can only be mapped to the top-level element.");

            if (currentSegment.EpmInfo != null)
            {
                if (currentSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty)
                {
                    Debug.Assert(
                        epmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty,
                        "MultiValue property values can't be mapped directly to the top-level element content (no such syndication mapping exists).");

                    // The info we're trying to add is a multiValue property, which would mean two multiValue properties trying to map to the same top-level element.
                    // This can happen if the base type defines mapping for a multiValue property and the derived type defines it again
                    //   in which case we will try to add the derived type mapping again.
                    // So we need to check that these properties are from the same source
                    // It is ok to map to atom:category and atom:link elements with different sources only if the criteria values are different.
                    if (epmInfo.Attribute.SourcePath != currentSegment.EpmInfo.Attribute.SourcePath)
                    {
                        if (epmInfo.CriteriaValue != null)
                        {
                            throw new ODataException(Strings.EpmTargetTree_TwoMultiValuePropertiesMappedToTheSameConditionalTopLevelElement(
                                                         currentSegment.EpmInfo.Attribute.SourcePath,
                                                         epmInfo.Attribute.SourcePath,
                                                         epmInfo.DefiningType.Name,
                                                         epmInfo.CriteriaValue));
                        }
                        else
                        {
                            throw new ODataException(Strings.EpmTargetTree_TwoMultiValuePropertiesMappedToTheSameTopLevelElement(
                                                         currentSegment.EpmInfo.Attribute.SourcePath,
                                                         epmInfo.Attribute.SourcePath,
                                                         epmInfo.DefiningType.Name));
                        }
                    }

                    Debug.Assert(
                        !foundSegment.EpmInfo.DefiningTypesAreEqual(epmInfo),
                        "Trying to add a multiValue property mapping for the same property on the same type twice. The souce tree should have prevented this from happening.");

                    // If the sources are the same (and the types are different), we can safely overwrite the epmInfo
                    // with the new one (which is for the derived type)
                    // The epm info is stored below.
                }
                else
                {
                    Debug.Assert(
                        epmInfo.MultiValueStatus != EntityPropertyMappingMultiValueStatus.MultiValueProperty,
                        "Only non-multiValue propeties should get here, we cover the rest above.");

                    // Two EpmAttributes with same TargetName in the inheritance hierarchy
                    throw new ODataException(Strings.EpmTargetTree_DuplicateEpmAttrsWithSameTargetName(EpmTargetTree.GetPropertyNameFromEpmInfo(currentSegment.EpmInfo), currentSegment.EpmInfo.DefiningType.Name, currentSegment.EpmInfo.Attribute.SourcePath, epmInfo.Attribute.SourcePath));
                }
            }

            // Increment the number of properties for which KeepInContent is false
            if (!epmInfo.Attribute.KeepInContent)
            {
                if (epmInfo.IsAtomLinkMapping || epmInfo.IsAtomCategoryMapping)
                {
                    this.countOfNonContentV3mappings++;
                }
                else
                {
                    this.countOfNonContentV2mappings++;
                }
            }

            currentSegment.EpmInfo = epmInfo;

            // Mixed content is dis-allowed. Since root has no ancestor, pass in false for ancestorHasContent
            if (EpmTargetTree.HasMixedContent(this.NonSyndicationRoot, false))
            {
                throw new ODataException(Strings.EpmTargetTree_InvalidTargetPath(targetPath));
            }
        }