/// <summary>
        /// Reads a property value starting on a complex value.
        /// </summary>
        /// <param name="complexValue">The complex value to start with.</param>
        /// <param name="complexPropertySegment">The EPM source path segment which points to the <paramref name="complexValue"/>.</param>
        /// <param name="epmValueCache">The EPM value cache to use.</param>
        /// <param name="sourceSegmentIndex">The index in the property value path to start with.</param>
        /// <param name="resourceType">The resource type of the complex value.</param>
        /// <param name="metadata">The metadata provider to use.</param>
        /// <param name="nullOnParentProperty">true if the value of the property is null because one of its parent properties was null, in this case
        /// the return value of the method is always null. false if the value of the property is the actual property value which may or may not be null.</param>
        /// <returns>The value of the property (may be null), or null if the property itself was not found due to one of its parent properties being null.</returns>
        private object ReadComplexPropertyValue(
            ODataComplexValue complexValue,
            EpmSourcePathSegment complexPropertySegment,
            EpmValueCache epmValueCache,
            int sourceSegmentIndex,
            ResourceType resourceType,
            DataServiceMetadataProviderWrapper metadata,
            out bool nullOnParentProperty)
        {
            Debug.Assert(this.propertyValuePath != null, "The propertyValuePath should have been initialized by now.");
            Debug.Assert(this.propertyValuePath.Length > sourceSegmentIndex, "The propertyValuePath must be at least as long as the source segment index.");
            Debug.Assert(epmValueCache != null, "epmValueCache != null");
            Debug.Assert(sourceSegmentIndex >= 0, "sourceSegmentIndex >= 0");
            Debug.Assert(resourceType != null, "resourceType != null");

            if (complexValue == null)
            {
                nullOnParentProperty = true;
                return(null);
            }

            return(this.ReadPropertyValue(
                       EpmValueCache.GetComplexValueProperties(epmValueCache, complexPropertySegment, complexValue, false),
                       sourceSegmentIndex,
                       resourceType,
                       metadata,
                       epmValueCache,
                       out nullOnParentProperty));
        }
        /// <summary>
        /// Writes out the value of a complex property.
        /// </summary>
        /// <param name="writer">The <see cref="XmlWriter"/> to write to.</param>
        /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param>
        /// <param name="complexValue">The complex value to write.</param>
        /// <param name="metadataType">The metadata type for the complex value.</param>
        /// <param name="isOpenPropertyType">True if the type name belongs to an open property.</param>
        /// <param name="isWritingCollection">True if we are writing a collection instead of an entry.</param>
        /// <param name="version">The protocol version used for writing.</param>
        /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param>
        /// <param name="epmSourcePathSegment">The EPM source path segment which points to the property we're writing. (can be null)</param>
        internal static void WriteComplexValue(
            XmlWriter writer,
            DataServiceMetadataProviderWrapper metadata,
            ODataComplexValue complexValue,
            ResourceType metadataType,
            bool isOpenPropertyType,
            bool isWritingCollection,
            ODataVersion version,
            EpmValueCache epmValueCache,
            EpmSourcePathSegment epmSourcePathSegment)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(complexValue != null, "complexValue != null");

            string typeName = complexValue.TypeName;

            // resolve the type name to the resource type; if no type name is specified we will use the
            // type inferred from metadata
            ResourceType complexValueType = MetadataUtils.ResolveTypeName(metadata, metadataType, ref typeName, ResourceTypeKind.ComplexType, isOpenPropertyType);

            if (typeName != null)
            {
                WritePropertyTypeAttribute(writer, typeName);
            }

            WriteProperties(
                writer,
                metadata,
                complexValueType,
                EpmValueCache.GetComplexValueProperties(epmValueCache, epmSourcePathSegment, complexValue, true),
                version,
                isWritingCollection,
                epmValueCache,
                epmSourcePathSegment);
        }
Example #3
0
        /// <summary>
        /// Constructor which creates an empty root.
        /// </summary>
        /// <param name="epmTargetTree">Target xml tree</param>
        internal EpmSourceTree(EpmTargetTree epmTargetTree)
        {
            DebugUtils.CheckNoExternalCallers();

            this.root          = new EpmSourcePathSegment();
            this.epmTargetTree = epmTargetTree;
        }
Example #4
0
        /// <summary>
        /// Returns the items for the specified multivalue.
        /// </summary>
        /// <param name="sourcePathSegment">The source path segment for the property which has this multivalue.</param>
        /// <param name="multiValue">The multivalue to get the items for.</param>
        /// <param name="writingContent">true if we're writing entry content or false when writing out-of-content EPM.</param>
        /// <returns>The items enumeration for the multivalue.</returns>
        private IEnumerable GetMultiValueItems(EpmSourcePathSegment sourcePathSegment, ODataMultiValue multiValue, bool writingContent)
        {
            Debug.Assert(writingContent || sourcePathSegment != null, "sourcePathSegment must be specified when writing out-of-content.");
            Debug.Assert(multiValue != null, "multiValue != null");

            // If we're writing into content we don't want to populate the cache if it's not already populated.
            // The goal is to behave the same with and without EPM.
            if (writingContent && this.epmValuesCache == null)
            {
                return(multiValue.Items);
            }

            object cachedItemsValue;

            if (this.epmValuesCache != null && this.epmValuesCache.TryGetValue(sourcePathSegment, out cachedItemsValue))
            {
                Debug.Assert(cachedItemsValue is List <object>, "The cached value for multi value must be a List of object");
                return((IEnumerable)cachedItemsValue);
            }

            IEnumerable   items       = multiValue.Items;
            List <object> cachedItems = null;

            if (items != null)
            {
                cachedItems = new List <object>();
                foreach (object item in items)
                {
                    // If the value is a complex value, store it as EpmMultiValueItemCache instance, so that we have a place
                    // to cache the enumeration of properties on that complex value (and possible other nested complex/multi values).
                    if (item is ODataComplexValue)
                    {
                        cachedItems.Add(new EpmMultiValueItemCache(item));
                    }
                    else
                    {
                        // Otherwise it should be a primitive value and thus we can just cache the value itself as it won't have any children
                        cachedItems.Add(item);
                    }
                }
            }

            if (this.epmValuesCache == null)
            {
                this.epmValuesCache = new Dictionary <EpmSourcePathSegment, object>(ReferenceEqualityComparer <EpmSourcePathSegment> .Instance);
            }

            this.epmValuesCache.Add(sourcePathSegment, cachedItems);
            return(cachedItems);
        }
        /// <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 #6
0
        /// <summary>
        /// Validates the specified segment and all its subsegments.
        /// </summary>
        /// <param name="pathSegment">The path segment to validate.</param>
        /// <param name="resourceType">The resource type of the property represented by this segment (null for open properties).</param>
        private static void Validate(EpmSourcePathSegment pathSegment, ResourceType resourceType)
        {
            Debug.Assert(pathSegment != null, "pathSegment != null");

            foreach (EpmSourcePathSegment subSegment in pathSegment.SubProperties)
            {
                bool         isMultiValueProperty;
                ResourceType subSegmentResourceType = GetPropertyType(resourceType, subSegment.PropertyName, out isMultiValueProperty);

                if (isMultiValueProperty)
                {
                    ValidateMultiValueSegment(subSegment.EpmInfo, subSegment, subSegmentResourceType);
                }
                else
                {
                    Validate(subSegment, subSegmentResourceType);
                }
            }
        }
Example #7
0
        /// <summary>
        /// Returns the properties for the specified complex value.
        /// </summary>
        /// <param name="epmValueCache">The EPM value cache to use (can be null).</param>
        /// <param name="sourcePathSegment">The source path segment for the property which has this complex value.</param>
        /// <param name="complexValue">The complex value to get the properties for.</param>
        /// <param name="writingContent">If we're writing content of an entry or not.</param>
        /// <returns>The properties enumeration for the complex value.</returns>
        internal static IEnumerable<ODataProperty> GetComplexValueProperties(
            EpmValueCache epmValueCache, 
            EpmSourcePathSegment sourcePathSegment, 
            ODataComplexValue complexValue, 
            bool writingContent)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(complexValue != null, "complexValue != null");
            Debug.Assert(writingContent || epmValueCache != null, "If we're not writing content, then the EPM value cache must exist.");

            if (epmValueCache == null)
            {
                return complexValue.Properties;
            }
            else
            {
                return epmValueCache.GetComplexValueProperties(sourcePathSegment, complexValue, writingContent);
            }
        }
Example #8
0
        /// <summary>
        /// Returns the items for the specified multivalue.
        /// </summary>
        /// <param name="epmValueCache">The EPM value cache to use (can be null).</param>
        /// <param name="sourcePathSegment">The source path segment for the property which has this multivalue.</param>
        /// <param name="multiValue">The multivalue to get the items for.</param>
        /// <param name="writingContent">If we're writing content of an entry or not.</param>
        /// <returns>The items enumeration for the multivalue.</returns>
        internal static IEnumerable GetMultiValueItems(
            EpmValueCache epmValueCache,
            EpmSourcePathSegment sourcePathSegment,
            ODataMultiValue multiValue,
            bool writingContent)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(multiValue != null, "multiValue != null");
            Debug.Assert(writingContent || epmValueCache != null, "If we're not writing content, then the EPM value cache must exist.");

            if (epmValueCache == null)
            {
                return(multiValue.Items);
            }
            else
            {
                return(epmValueCache.GetMultiValueItems(sourcePathSegment, multiValue, writingContent));
            }
        }
Example #9
0
        /// <summary>
        /// Returns the items for the specified multivalue.
        /// </summary>
        /// <param name="epmValueCache">The EPM value cache to use (can be null).</param>
        /// <param name="sourcePathSegment">The source path segment for the property which has this multivalue.</param>
        /// <param name="multiValue">The multivalue to get the items for.</param>
        /// <param name="writingContent">If we're writing content of an entry or not.</param>
        /// <returns>The items enumeration for the multivalue.</returns>
        internal static IEnumerable GetMultiValueItems(
            EpmValueCache epmValueCache,
            EpmSourcePathSegment sourcePathSegment, 
            ODataMultiValue multiValue, 
            bool writingContent)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(multiValue != null, "multiValue != null");
            Debug.Assert(writingContent || epmValueCache != null, "If we're not writing content, then the EPM value cache must exist.");

            if (epmValueCache == null)
            {
                return multiValue.Items;
            }
            else
            {
                return epmValueCache.GetMultiValueItems(sourcePathSegment, multiValue, writingContent);
            }
        }
Example #10
0
        /// <summary>
        /// Returns the properties for the specified complex value.
        /// </summary>
        /// <param name="epmValueCache">The EPM value cache to use (can be null).</param>
        /// <param name="sourcePathSegment">The source path segment for the property which has this complex value.</param>
        /// <param name="complexValue">The complex value to get the properties for.</param>
        /// <param name="writingContent">If we're writing content of an entry or not.</param>
        /// <returns>The properties enumeration for the complex value.</returns>
        internal static IEnumerable <ODataProperty> GetComplexValueProperties(
            EpmValueCache epmValueCache,
            EpmSourcePathSegment sourcePathSegment,
            ODataComplexValue complexValue,
            bool writingContent)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(complexValue != null, "complexValue != null");
            Debug.Assert(writingContent || epmValueCache != null, "If we're not writing content, then the EPM value cache must exist.");

            if (epmValueCache == null)
            {
                return(complexValue.Properties);
            }
            else
            {
                return(epmValueCache.GetComplexValueProperties(sourcePathSegment, complexValue, writingContent));
            }
        }
        /// <summary>
        /// Write the given collection of properties.
        /// </summary>
        /// <param name="writer">The <see cref="XmlWriter"/> to write to.</param>
        /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param>
        /// <param name="owningType">The <see cref="ResourceType"/> of the entry (or null if not metadata is available).</param>
        /// <param name="cachedProperties">Collection of cached properties for the entry.</param>
        /// <param name="version">The protocol version used for writing.</param>
        /// <param name="isWritingCollection">True if we are writing a collection instead of an entry.</param>
        /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param>
        /// <param name="epmSourcePathSegment">The EPM source path segment which points to the property which sub-properites we're writing. (can be null)</param>
        internal static void WriteProperties(
            XmlWriter writer,
            DataServiceMetadataProviderWrapper metadata,
            ResourceType owningType,
            IEnumerable <ODataProperty> cachedProperties,
            ODataVersion version,
            bool isWritingCollection,
            EpmValueCache epmValueCache,
            EpmSourcePathSegment epmSourcePathSegment)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(writer != null, "writer != null");

            if (cachedProperties == null)
            {
                return;
            }

            foreach (ODataProperty property in cachedProperties)
            {
                WriteProperty(writer, metadata, property, owningType, version, false, isWritingCollection, epmValueCache, epmSourcePathSegment);
            }
        }
Example #12
0
        /// <summary>
        /// Returns the properties for the specified complex value.
        /// </summary>
        /// <param name="sourcePathSegment">The source path segment for the property which has this complex value.</param>
        /// <param name="complexValue">The complex value to get the properties for.</param>
        /// <param name="writingContent">true if we're writing entry content or false when writing out-of-content EPM.</param>
        /// <returns>The properties enumeration for the complex value.</returns>
        private IEnumerable <ODataProperty> GetComplexValueProperties(EpmSourcePathSegment sourcePathSegment, ODataComplexValue complexValue, bool writingContent)
        {
            Debug.Assert(writingContent || sourcePathSegment != null, "sourcePathSegment must be specified when writing out-of-content.");
            Debug.Assert(complexValue != null, "complexValue != null");

            // If we're writing into content we don't want to populate the cache if it's not already populated.
            // The goal is to behave the same with and without EPM.
            if (writingContent && this.epmValuesCache == null)
            {
                return(complexValue.Properties);
            }

            object cachedPropertiesValue;

            if (this.epmValuesCache != null && this.epmValuesCache.TryGetValue(sourcePathSegment, out cachedPropertiesValue))
            {
                Debug.Assert(cachedPropertiesValue is List <ODataProperty>, "The cached value for complex type must be a List of ODataProperty");
                return((IEnumerable <ODataProperty>)cachedPropertiesValue);
            }

            IEnumerable <ODataProperty> properties       = complexValue.Properties;
            List <ODataProperty>        cachedProperties = null;

            if (properties != null)
            {
                cachedProperties = new List <ODataProperty>(properties);
            }

            if (this.epmValuesCache == null)
            {
                this.epmValuesCache = new Dictionary <EpmSourcePathSegment, object>(ReferenceEqualityComparer <EpmSourcePathSegment> .Instance);
            }

            this.epmValuesCache.Add(sourcePathSegment, cachedProperties);
            return(cachedProperties);
        }
Example #13
0
        /// <summary>
        /// Returns the items for the specified multivalue.
        /// </summary>
        /// <param name="sourcePathSegment">The source path segment for the property which has this multivalue.</param>
        /// <param name="multiValue">The multivalue to get the items for.</param>
        /// <param name="writingContent">true if we're writing entry content or false when writing out-of-content EPM.</param>
        /// <returns>The items enumeration for the multivalue.</returns>
        private IEnumerable GetMultiValueItems(EpmSourcePathSegment sourcePathSegment, ODataMultiValue multiValue, bool writingContent)
        {
            Debug.Assert(writingContent || sourcePathSegment != null, "sourcePathSegment must be specified when writing out-of-content.");
            Debug.Assert(multiValue != null, "multiValue != null");

            // If we're writing into content we don't want to populate the cache if it's not already populated.
            // The goal is to behave the same with and without EPM.
            if (writingContent && this.epmValuesCache == null)
            {
                return multiValue.Items;
            }

            object cachedItemsValue;
            if (this.epmValuesCache != null && this.epmValuesCache.TryGetValue(sourcePathSegment, out cachedItemsValue))
            {
                Debug.Assert(cachedItemsValue is List<object>, "The cached value for multi value must be a List of object");
                return (IEnumerable)cachedItemsValue;
            }

            IEnumerable items = multiValue.Items;
            List<object> cachedItems = null;
            if (items != null)
            {
                cachedItems = new List<object>();
                foreach (object item in items)
                {
                    // If the value is a complex value, store it as EpmMultiValueItemCache instance, so that we have a place
                    // to cache the enumeration of properties on that complex value (and possible other nested complex/multi values).
                    if (item is ODataComplexValue)
                    {
                        cachedItems.Add(new EpmMultiValueItemCache(item));
                    }
                    else
                    {
                        // Otherwise it should be a primitive value and thus we can just cache the value itself as it won't have any children
                        cachedItems.Add(item);
                    }
                }
            }

            if (this.epmValuesCache == null)
            {
                this.epmValuesCache = new Dictionary<EpmSourcePathSegment, object>(ReferenceEqualityComparer<EpmSourcePathSegment>.Instance);
            }

            this.epmValuesCache.Add(sourcePathSegment, cachedItems);
            return cachedItems;
        }
Example #14
0
        /// <summary>
        /// Returns the properties for the specified complex value.
        /// </summary>
        /// <param name="sourcePathSegment">The source path segment for the property which has this complex value.</param>
        /// <param name="complexValue">The complex value to get the properties for.</param>
        /// <param name="writingContent">true if we're writing entry content or false when writing out-of-content EPM.</param>
        /// <returns>The properties enumeration for the complex value.</returns>
        private IEnumerable<ODataProperty> GetComplexValueProperties(EpmSourcePathSegment sourcePathSegment, ODataComplexValue complexValue, bool writingContent)
        {
            Debug.Assert(writingContent || sourcePathSegment != null, "sourcePathSegment must be specified when writing out-of-content.");
            Debug.Assert(complexValue != null, "complexValue != null");

            // If we're writing into content we don't want to populate the cache if it's not already populated.
            // The goal is to behave the same with and without EPM.
            if (writingContent && this.epmValuesCache == null)
            {
                return complexValue.Properties;
            }

            object cachedPropertiesValue;
            if (this.epmValuesCache != null && this.epmValuesCache.TryGetValue(sourcePathSegment, out cachedPropertiesValue))
            {
                Debug.Assert(cachedPropertiesValue is List<ODataProperty>, "The cached value for complex type must be a List of ODataProperty");
                return (IEnumerable<ODataProperty>)cachedPropertiesValue;
            }

            IEnumerable<ODataProperty> properties = complexValue.Properties;
            List<ODataProperty> cachedProperties = null;
            if (properties != null)
            {
                cachedProperties = new List<ODataProperty>(properties);
            }

            if (this.epmValuesCache == null)
            {
                this.epmValuesCache = new Dictionary<EpmSourcePathSegment, object>(ReferenceEqualityComparer<EpmSourcePathSegment>.Instance);
            }

            this.epmValuesCache.Add(sourcePathSegment, cachedProperties);
            return cachedProperties;
        }
Example #15
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);
        }
        /// <summary>
        /// Writes a single property in ATOM format.
        /// </summary>
        /// <param name="writer">The <see cref="XmlWriter"/> to write to.</param>
        /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param>
        /// <param name="property">The property to write out.</param>
        /// <param name="owningType">The type owning the property (or null if no metadata is available).</param>
        /// <param name="version">The protocol version used for writing.</param>
        /// <param name="isTopLevel">True if writing a top-level property payload; otherwise false.</param>
        /// <param name="isWritingCollection">True if we are writing a collection instead of an entry.</param>
        /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param>
        /// <param name="epmParentSourcePathSegment">The EPM source path segment which points to the property which sub-property we're writing. (can be null)</param>
        internal static void WriteProperty(
            XmlWriter writer,
            DataServiceMetadataProviderWrapper metadata,
            ODataProperty property,
            ResourceType owningType,
            ODataVersion version,
            bool isTopLevel,
            bool isWritingCollection,
            EpmValueCache epmValueCache,
            EpmSourcePathSegment epmParentSourcePathSegment)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(writer != null, "writer != null");

            ValidationUtils.ValidateProperty(property);
            ResourceProperty resourceProperty = ValidationUtils.ValidatePropertyDefined(property.Name, owningType);

            EpmSourcePathSegment epmSourcePathSegment = null;

            if (epmParentSourcePathSegment != null)
            {
                epmSourcePathSegment = epmParentSourcePathSegment.SubProperties.Where(subProperty => subProperty.PropertyName == property.Name).FirstOrDefault();
            }

            object value = property.Value;

            // TODO: If we implement validation or type conversions the value needs to be converted here
            //       since the next method call needs to know if the value is a string or not in some cases.

            // If EPM tells us to skip this property in content, then we're done here.
            if (!ShouldWritePropertyInContent(value, epmSourcePathSegment, version))
            {
                return;
            }

            // <d:propertyname>
            writer.WriteStartElement(
                isWritingCollection ? string.Empty : AtomConstants.ODataNamespacePrefix,
                property.Name,
                AtomConstants.ODataNamespace);

            if (isTopLevel)
            {
                WriteDefaultNamespaceAttributes(writer, DefaultNamespaceFlags.OData | DefaultNamespaceFlags.ODataMetadata);
            }

            // Null property value.
            if (value == null)
            {
                // verify that MultiValue properties are not null
                if (resourceProperty != null && resourceProperty.Kind == ResourcePropertyKind.MultiValue)
                {
                    throw new ODataException(Strings.ODataWriter_MultiValuePropertiesMustNotHaveNullValue(resourceProperty.Name));
                }

                ODataAtomWriterUtils.WriteNullAttribute(writer);
            }
            else
            {
                ODataComplexValue complexValue         = value as ODataComplexValue;
                ResourceType      resourcePropertyType = resourceProperty == null ? null : resourceProperty.ResourceType;
                bool isOpenPropertyType = owningType != null && owningType.IsOpenType && resourceProperty == null;

                // Complex properties are written recursively.
                if (complexValue != null)
                {
                    WriteComplexValue(writer, metadata, complexValue, resourcePropertyType, isOpenPropertyType, isWritingCollection, version, epmValueCache, epmSourcePathSegment);
                }
                else
                {
                    ODataMultiValue multiValue = value as ODataMultiValue;
                    if (multiValue != null)
                    {
                        ODataVersionChecker.CheckMultiValueProperties(version, property.Name);
                        WriteMultiValue(writer, metadata, multiValue, resourcePropertyType, isOpenPropertyType, isWritingCollection, version, epmValueCache, epmSourcePathSegment);
                    }
                    else
                    {
                        WritePrimitiveValue(writer, value, resourcePropertyType);
                    }
                }
            }

            // </d:propertyname>
            writer.WriteEndElement();
        }
Example #17
0
        /// <summary>
        /// Write the content of the given entry.
        /// </summary>
        /// <param name="entry">The entry for which to write properties.</param>
        /// <param name="entryType">The <see cref="ResourceType"/> of the entry (or null if not metadata is available).</param>
        /// <param name="propertiesValueCache">The cache of properties.</param>
        /// <param name="rootSourcePathSegment">The root of the EPM source tree, if there's an EPM applied.</param>
        private void WriteEntryContent(ODataEntry entry, ResourceType entryType, EntryPropertiesValueCache propertiesValueCache, EpmSourcePathSegment rootSourcePathSegment)
        {
            Debug.Assert(entry != null, "entry != null");
            Debug.Assert(propertiesValueCache != null, "propertiesValueCache != null");

            ODataMediaResource mediaResource = entry.MediaResource;
            if (mediaResource == null)
            {
                // <content type="application/xml">
                this.writer.WriteStartElement(
                    AtomConstants.AtomNamespacePrefix,
                    AtomConstants.AtomContentElementName,
                    AtomConstants.AtomNamespace);

                this.writer.WriteAttributeString(
                    AtomConstants.AtomTypeAttributeName,
                    MimeConstants.MimeApplicationXml);

                // <m:properties>
                // we always write the <m:properties> element even if there are no properties
                this.writer.WriteStartElement(
                    AtomConstants.ODataMetadataNamespacePrefix,
                    AtomConstants.AtomPropertiesElementName,
                    AtomConstants.ODataMetadataNamespace);

                ODataAtomWriterUtils.WriteProperties(
                    this.writer, 
                    this.MetadataProvider,
                    entryType,
                    propertiesValueCache.EntryProperties,
                    this.Version,
                    false,
                    propertiesValueCache,
                    rootSourcePathSegment);

                // </m:properties>
                this.writer.WriteEndElement();

                // </content>
                this.writer.WriteEndElement();
            }
            else
            {
                Uri mediaEditLink = mediaResource.EditLink;
                if (mediaEditLink != null)
                {
                    // <link rel="edit-media" href="href" />
                    this.writer.WriteStartElement(
                        AtomConstants.AtomNamespacePrefix,
                        AtomConstants.AtomLinkElementName,
                        AtomConstants.AtomNamespace);

                    this.writer.WriteAttributeString(
                        AtomConstants.AtomLinkRelationAttributeName,
                        AtomConstants.AtomEditMediaRelationAttributeValue);

                    this.writer.WriteAttributeString(
                        AtomConstants.AtomHRefAttributeName,
                        AtomUtils.ToUrlAttributeValue(mediaEditLink, this.BaseUri));

                    string mediaETag = mediaResource.ETag;
                    if (mediaETag != null)
                    {
                        this.writer.WriteAttributeString(
                            AtomConstants.ODataMetadataNamespacePrefix,
                            AtomConstants.ODataETagAttributeName,
                            AtomConstants.ODataMetadataNamespace,
                            mediaETag);
                    }

                    // </link>
                    this.writer.WriteEndElement();
                }

                Debug.Assert(mediaEditLink != null || mediaResource.ETag == null, "The default stream edit link and etag should have been validated by now.");

                // <content type="type" src="src">
                this.writer.WriteStartElement(
                    AtomConstants.AtomNamespacePrefix,
                    AtomConstants.AtomContentElementName,
                    AtomConstants.AtomNamespace);

                Debug.Assert(!string.IsNullOrEmpty(mediaResource.ContentType), "The default stream content type should have been validated by now.");
                this.writer.WriteAttributeString(
                    AtomConstants.AtomTypeAttributeName,
                    mediaResource.ContentType);

                Debug.Assert(mediaResource.ReadLink != null, "The default stream read link should have been validated by now.");
                this.writer.WriteAttributeString(
                    AtomConstants.MediaLinkEntryContentSourceAttributeName,
                    AtomUtils.ToUrlAttributeValue(mediaResource.ReadLink, this.BaseUri));

                // </content>
                this.writer.WriteEndElement();

                // <m:properties>
                // we always write the <m:properties> element even if there are no properties
                this.writer.WriteStartElement(
                    AtomConstants.ODataMetadataNamespacePrefix,
                    AtomConstants.AtomPropertiesElementName,
                    AtomConstants.ODataMetadataNamespace);

                ODataAtomWriterUtils.WriteProperties(
                    this.writer, 
                    this.MetadataProvider,
                    entryType,
                    propertiesValueCache.EntryProperties, 
                    this.Version,
                    false,
                    propertiesValueCache,
                    rootSourcePathSegment);

                // </m:properties>
                this.writer.WriteEndElement();
            }
        }
        /// <summary>
        /// Reads a property value starting with the specified index to the property value path.
        /// </summary>
        /// <param name="cachedProperties">The enumeration of properties to search for the first property in the property value path.</param>
        /// <param name="sourceSegmentIndex">The index in the property value path to start with.</param>
        /// <param name="resourceType">The resource type of the entry or complex value the <paramref name="cachedProperties"/> enumeration belongs to.</param>
        /// <param name="metadata">The metadata provider to use.</param>
        /// <param name="epmValueCache">The EPM value cache to use.</param>
        /// <param name="nullOnParentProperty">true if the value of the property is null because one of its parent properties was null, in this case
        /// the return value of the method is always null. false if the value of the property is the actual property value which may or may not be null.</param>
        /// <returns>The value of the property (may be null), or null if the property itself was not found due to one of its parent properties being null.</returns>
        private object ReadPropertyValue(
            IEnumerable <ODataProperty> cachedProperties,
            int sourceSegmentIndex,
            ResourceType resourceType,
            DataServiceMetadataProviderWrapper metadata,
            EpmValueCache epmValueCache,
            out bool nullOnParentProperty)
        {
            Debug.Assert(this.propertyValuePath != null, "The propertyValuePath should have been initialized by now.");
            Debug.Assert(this.propertyValuePath.Length > sourceSegmentIndex, "The propertyValuePath must be at least as long as the source segment index.");
            Debug.Assert(resourceType != null, "resourceType != null");
            Debug.Assert(epmValueCache != null, "epmValueCache != null");

            EpmSourcePathSegment sourceSegment = this.propertyValuePath[sourceSegmentIndex];
            string propertyName = sourceSegment.PropertyName;
            bool   lastSegment  = this.propertyValuePath.Length == sourceSegmentIndex + 1;

            ResourceProperty resourceProperty = ValidationUtils.ValidatePropertyDefined(propertyName, resourceType);

            if (resourceProperty != null)
            {
                // If this is the last part of the path, then it has to be a primitive or multiValue type otherwise should be a complex type
                if (lastSegment)
                {
                    if (!resourceProperty.IsOfKind(ResourcePropertyKind.Primitive) && !resourceProperty.IsOfKind(ResourcePropertyKind.MultiValue))
                    {
                        throw new ODataException(Strings.EpmSourceTree_EndsWithNonPrimitiveType(propertyName));
                    }
                }
                else
                {
                    if (!resourceProperty.IsOfKind(ResourcePropertyKind.ComplexType))
                    {
                        throw new ODataException(Strings.EpmSourceTree_TraversalOfNonComplexType(propertyName));
                    }
                }
            }
            else
            {
                Debug.Assert(
                    resourceType.IsOpenType,
                    "Only open types can have undeclared properties, otherwise we should have failed in the ValidatePropertyDefined method.");
            }

            ODataProperty property = null;

            if (cachedProperties != null)
            {
                property = cachedProperties.FirstOrDefault(p => p.Name == propertyName);
            }

            if (property == null)
            {
                throw new ODataException(Strings.EpmSourceTree_MissingPropertyOnInstance(propertyName, resourceType.FullName));
            }

            object            propertyValue        = property.Value;
            ODataComplexValue propertyComplexValue = propertyValue as ODataComplexValue;

            if (lastSegment)
            {
                if (propertyValue != null && resourceProperty != null)
                {
                    ValidationUtils.ValidateIsExpectedPrimitiveType(propertyValue, resourceProperty.ResourceType);
                }

                // If this property is the last one it has to be either a primitive or multivalue
                // TODO: Check for named streams here as well
                if (propertyComplexValue != null)
                {
                    throw new ODataException(Strings.EpmSourceTree_EndsWithNonPrimitiveType(propertyName));
                }

                nullOnParentProperty = false;
                return(propertyValue);
            }
            else
            {
                // Otherwise it's in the middle and thus it must be a complex value
                if (propertyComplexValue == null)
                {
                    throw new ODataException(Strings.EpmSourceTree_TraversalOfNonComplexType(propertyName));
                }

                string       typeName         = propertyComplexValue.TypeName;
                ResourceType complexValueType = MetadataUtils.ResolveTypeName(
                    metadata,
                    resourceProperty == null ? null : resourceProperty.ResourceType,
                    ref typeName,
                    ResourceTypeKind.ComplexType,
                    resourceProperty == null);

                return(this.ReadComplexPropertyValue(
                           propertyComplexValue,
                           sourceSegment,
                           epmValueCache,
                           sourceSegmentIndex + 1,
                           complexValueType,
                           metadata,
                           out nullOnParentProperty));
            }
        }
        /// <summary>
        /// Write the items in a MultiValue in ATOM format.
        /// </summary>
        /// <param name="writer">The <see cref="XmlWriter"/> to write to.</param>
        /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param>
        /// <param name="multiValue">The MultiValue to write.</param>
        /// <param name="resourcePropertyType">The resource type of the multi value (or null if not metadata is available).</param>
        /// <param name="isOpenPropertyType">True if the type name belongs to an open property.</param>
        /// <param name="isWritingCollection">True if we are writing a collection instead of an entry.</param>
        /// <param name="version">The protocol version used for writing.</param>
        /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param>
        /// <param name="epmSourcePathSegment">The EPM source path segment which points to the multivalue property we're writing. (can be null)</param>
        private static void WriteMultiValue(
            XmlWriter writer,
            DataServiceMetadataProviderWrapper metadata,
            ODataMultiValue multiValue,
            ResourceType resourcePropertyType,
            bool isOpenPropertyType,
            bool isWritingCollection,
            ODataVersion version,
            EpmValueCache epmValueCache,
            EpmSourcePathSegment epmSourcePathSegment)
        {
            Debug.Assert(multiValue != null, "multiValue != null");

            string typeName = multiValue.TypeName;

            // resolve the type name to the resource type; if no type name is specified we will use the
            // type inferred from metadata
            MultiValueResourceType multiValueType = (MultiValueResourceType)MetadataUtils.ResolveTypeName(metadata, resourcePropertyType, ref typeName, ResourceTypeKind.MultiValue, isOpenPropertyType);

            if (typeName != null)
            {
                WritePropertyTypeAttribute(writer, typeName);
            }

            ResourceType expectedItemType = multiValueType == null ? null : multiValueType.ItemType;

            IEnumerable items = EpmValueCache.GetMultiValueItems(epmValueCache, epmSourcePathSegment, multiValue, true);

            if (items != null)
            {
                foreach (object itemValue in items)
                {
                    object item;
                    EpmMultiValueItemCache epmItemCache = itemValue as EpmMultiValueItemCache;
                    if (epmItemCache != null)
                    {
                        item = epmItemCache.ItemValue;
                    }
                    else
                    {
                        item = itemValue;
                    }

                    ValidationUtils.ValidateMultiValueItem(item);

                    writer.WriteStartElement(AtomConstants.ODataNamespacePrefix, AtomConstants.ODataMultiValueItemElementName, AtomConstants.ODataNamespace);
                    ODataComplexValue complexValue = item as ODataComplexValue;
                    if (complexValue != null)
                    {
                        WriteComplexValue(writer, metadata, complexValue, expectedItemType, false, isWritingCollection, version, epmItemCache, epmSourcePathSegment);
                    }
                    else
                    {
                        ODataMultiValue multiValueItem = item as ODataMultiValue;
                        if (multiValueItem != null)
                        {
                            throw new ODataException(Strings.ODataWriter_NestedMultiValuesAreNotSupported);
                        }
                        else
                        {
                            AtomValueUtils.WritePrimitiveValue(writer, item, expectedItemType);
                        }
                    }

                    writer.WriteEndElement();
                }
            }
        }
Example #20
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 #21
0
        /// <summary>
        /// Write the content of the given entry.
        /// </summary>
        /// <param name="entry">The entry for which to write properties.</param>
        /// <param name="entryType">The <see cref="ResourceType"/> of the entry (or null if not metadata is available).</param>
        /// <param name="propertiesValueCache">The cache of properties.</param>
        /// <param name="rootSourcePathSegment">The root of the EPM source tree, if there's an EPM applied.</param>
        private void WriteEntryContent(ODataEntry entry, ResourceType entryType, EntryPropertiesValueCache propertiesValueCache, EpmSourcePathSegment rootSourcePathSegment)
        {
            Debug.Assert(entry != null, "entry != null");
            Debug.Assert(propertiesValueCache != null, "propertiesValueCache != null");

            ODataMediaResource mediaResource = entry.MediaResource;

            if (mediaResource == null)
            {
                // <content type="application/xml">
                this.writer.WriteStartElement(
                    AtomConstants.AtomNamespacePrefix,
                    AtomConstants.AtomContentElementName,
                    AtomConstants.AtomNamespace);

                this.writer.WriteAttributeString(
                    AtomConstants.AtomTypeAttributeName,
                    MimeConstants.MimeApplicationXml);

                // <m:properties>
                // we always write the <m:properties> element even if there are no properties
                this.writer.WriteStartElement(
                    AtomConstants.ODataMetadataNamespacePrefix,
                    AtomConstants.AtomPropertiesElementName,
                    AtomConstants.ODataMetadataNamespace);

                ODataAtomWriterUtils.WriteProperties(
                    this.writer,
                    this.MetadataProvider,
                    entryType,
                    propertiesValueCache.EntryProperties,
                    this.Version,
                    false,
                    propertiesValueCache,
                    rootSourcePathSegment);

                // </m:properties>
                this.writer.WriteEndElement();

                // </content>
                this.writer.WriteEndElement();
            }
            else
            {
                Uri mediaEditLink = mediaResource.EditLink;
                if (mediaEditLink != null)
                {
                    // <link rel="edit-media" href="href" />
                    this.writer.WriteStartElement(
                        AtomConstants.AtomNamespacePrefix,
                        AtomConstants.AtomLinkElementName,
                        AtomConstants.AtomNamespace);

                    this.writer.WriteAttributeString(
                        AtomConstants.AtomLinkRelationAttributeName,
                        AtomConstants.AtomEditMediaRelationAttributeValue);

                    this.writer.WriteAttributeString(
                        AtomConstants.AtomHRefAttributeName,
                        AtomUtils.ToUrlAttributeValue(mediaEditLink, this.BaseUri));

                    string mediaETag = mediaResource.ETag;
                    if (mediaETag != null)
                    {
                        this.writer.WriteAttributeString(
                            AtomConstants.ODataMetadataNamespacePrefix,
                            AtomConstants.ODataETagAttributeName,
                            AtomConstants.ODataMetadataNamespace,
                            mediaETag);
                    }

                    // </link>
                    this.writer.WriteEndElement();
                }

                Debug.Assert(mediaEditLink != null || mediaResource.ETag == null, "The default stream edit link and etag should have been validated by now.");

                // <content type="type" src="src">
                this.writer.WriteStartElement(
                    AtomConstants.AtomNamespacePrefix,
                    AtomConstants.AtomContentElementName,
                    AtomConstants.AtomNamespace);

                Debug.Assert(!string.IsNullOrEmpty(mediaResource.ContentType), "The default stream content type should have been validated by now.");
                this.writer.WriteAttributeString(
                    AtomConstants.AtomTypeAttributeName,
                    mediaResource.ContentType);

                Debug.Assert(mediaResource.ReadLink != null, "The default stream read link should have been validated by now.");
                this.writer.WriteAttributeString(
                    AtomConstants.MediaLinkEntryContentSourceAttributeName,
                    AtomUtils.ToUrlAttributeValue(mediaResource.ReadLink, this.BaseUri));

                // </content>
                this.writer.WriteEndElement();

                // <m:properties>
                // we always write the <m:properties> element even if there are no properties
                this.writer.WriteStartElement(
                    AtomConstants.ODataMetadataNamespacePrefix,
                    AtomConstants.AtomPropertiesElementName,
                    AtomConstants.ODataMetadataNamespace);

                ODataAtomWriterUtils.WriteProperties(
                    this.writer,
                    this.MetadataProvider,
                    entryType,
                    propertiesValueCache.EntryProperties,
                    this.Version,
                    false,
                    propertiesValueCache,
                    rootSourcePathSegment);

                // </m:properties>
                this.writer.WriteEndElement();
            }
        }