/// <summary>
        /// Writes the custom mapped EPM properties to an XML writer which is expected to be positioned such to write
        /// a child element of the entry element.
        /// </summary>
        /// <param name="writer">The XmlWriter to write to.</param>
        /// <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>
        internal static void WriteEntryEpm(
            XmlWriter writer,
            EpmTargetTree epmTargetTree,
            EntryPropertiesValueCache epmValueCache,
            ResourceType resourceType,
            DataServiceMetadataProviderWrapper metadata)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(writer != null, "writer != null");
            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 custom mappings, just return null.
            EpmTargetPathSegment customRootSegment = epmTargetTree.NonSyndicationRoot;
            Debug.Assert(customRootSegment != null, "EPM Target tree must always have non-syndication root.");
            if (customRootSegment.SubSegments.Count == 0)
            {
                return;
            }

            foreach (EpmTargetPathSegment targetSegment in customRootSegment.SubSegments)
            {
                Debug.Assert(!targetSegment.IsAttribute, "Target segments under the custom root must be for elements only.");

                string alreadyDeclaredPrefix = null;
                WriteElementEpm(writer, targetSegment, epmValueCache, resourceType, metadata, ref alreadyDeclaredPrefix);
            }
        }
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="stream">The stream to write to.</param>
        /// <param name="odataWriterSettings">Configuration settings for the writer to create.</param>
        /// <param name="encoding">The encoding to use for writing.</param>
        /// <param name="writingResponse">True if the writer is to write a response payload; false if it's to write a request payload.</param>
        /// <param name="metadataProvider">The metadata provider to use.</param>
        /// <param name="writingFeed">True if the writer is created for writing a feed; false when it is created for writing an entry.</param>
        /// <param name="synchronous">True if the writer is created for synchronous operation; false for asynchronous.</param>
        internal ODataJsonWriter(
            Stream stream, 
            ODataWriterSettings odataWriterSettings, 
            Encoding encoding, 
            bool writingResponse,
            DataServiceMetadataProviderWrapper metadataProvider,
            bool writingFeed,
            bool synchronous)
            : base(
                odataWriterSettings.Version, 
                odataWriterSettings.BaseUri, 
                writingResponse, 
                metadataProvider,
                writingFeed,
                synchronous)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(stream != null, "stream != null");
            Debug.Assert(odataWriterSettings != null, "odataWriterSettings != null");

            this.outputStream = new AsyncBufferedStream(stream);
            this.textWriter = new StreamWriter(this.outputStream, encoding);

            this.jsonWriter = new JsonWriter(this.textWriter, odataWriterSettings.Indent);
        }
        /// <summary>
        /// Validates a type name to ensure that it's not an empty string.
        /// </summary>
        /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param>
        /// <param name="typeName">The type name to validate.</param>
        /// <param name="typeKind">The expected type kind for the given type name.</param>
        /// <param name="isOpenPropertyType">True if the type name belongs to an open property.</param>
        /// <returns>The resource type with the given name and kind if the metadata was available, otherwise null.</returns>
        internal static ResourceType ValidateTypeName(DataServiceMetadataProviderWrapper metadata, string typeName, ResourceTypeKind typeKind, bool isOpenPropertyType)
        {
            DebugUtils.CheckNoExternalCallers();

            if (typeName == null)
            {
                // if we have metadata the type name of an entry or a complex value of an open property must not be null
                if (metadata != null && (typeKind == ResourceTypeKind.EntityType || isOpenPropertyType))
                {
                    throw new ODataException(Strings.ODataWriterCore_MissingTypeNameWithMetadata);
                }

                return null;
            }

            // we do not allow empty type names
            if (typeName.Length == 0)
            {
                throw new ODataException(Strings.ODataWriter_TypeNameMustNotBeEmpty);
            }

            // If we do have metadata, lookup the type and translate it to ResourceType.
            ResourceType resourceType = null;
            if (metadata != null)
            {
                resourceType = typeKind == ResourceTypeKind.MultiValue
                    ? ValidateMultiValueTypeName(metadata, typeName)
                    : ValidateNonMultiValueTypeName(metadata, typeName, typeKind);
            }

            return resourceType;
        }
 /// <summary>
 /// Constructor.
 /// </summary>
 /// <param name="version">The version of the OData protocol to use.</param>
 /// <param name="metadataProvider">The metadata provider to use.</param>
 /// <param name="synchronous">True if the writer is created for synchronous operation; false for asynchronous.</param>
 protected ODataCollectionWriterCore(ODataVersion version, DataServiceMetadataProviderWrapper metadataProvider, bool synchronous)
 {
     DebugUtils.CheckNoExternalCallers();
     this.version = version;
     this.metadataProvider = metadataProvider;
     this.synchronous = synchronous;
     this.scopes.Push(new Scope(CollectionWriterState.Start, null));
 }
        /// <summary>
        /// Writes an EPM element target.
        /// </summary>
        /// <param name="writer">The writer to write to.</param>
        /// <param name="targetSegment">The target segment describing the element to write.</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="alreadyDeclaredPrefix">The name of the prefix if it was already declared.</param>
        private static void WriteElementEpm(
            XmlWriter writer, 
            EpmTargetPathSegment targetSegment, 
            EntryPropertiesValueCache epmValueCache, 
            ResourceType resourceType, 
            DataServiceMetadataProviderWrapper metadata,
            ref string alreadyDeclaredPrefix)
        {
            Debug.Assert(writer != null, "writer != null");
            Debug.Assert(targetSegment != null && !targetSegment.IsAttribute, "Only element target segments are supported by this method.");

            // If the prefix is null, the WCF DS will still write it as the default namespace, so we need it to be an empty string.
            string elementPrefix = targetSegment.SegmentNamespacePrefix ?? string.Empty;
            writer.WriteStartElement(elementPrefix, targetSegment.SegmentName, targetSegment.SegmentNamespaceUri);

            // Write out the declaration explicitly only if the prefix is not empty (just like the WCF DS does)
            if (elementPrefix.Length > 0)
            {
                WriteNamespaceDeclaration(writer, targetSegment, ref alreadyDeclaredPrefix);
            }

            // Serialize the sub segment attributes first
            foreach (EpmTargetPathSegment subSegment in targetSegment.SubSegments)
            {
                if (subSegment.IsAttribute)
                {
                    WriteAttributeEpm(writer, subSegment, epmValueCache, resourceType, metadata, ref alreadyDeclaredPrefix);
                }
            }

            if (targetSegment.HasContent)
            {
                Debug.Assert(!targetSegment.SubSegments.Any(subSegment => !subSegment.IsAttribute), "If the segment has a content, it must not have any element children.");

                string textPropertyValue = GetEntryPropertyValueAsText(targetSegment, epmValueCache, resourceType, metadata);

                // TODO: In V3 we should check for textPropertyValue == null and write out the m:null in that case.
                Debug.Assert(textPropertyValue != null, "Null property value should not get here, the GetEntryPropertyValueAsText should take care of that for now.");

                writer.WriteString(textPropertyValue);
            }
            else
            {
                // Serialize the sub segment elements now
                foreach (EpmTargetPathSegment subSegment in targetSegment.SubSegments)
                {
                    if (!subSegment.IsAttribute)
                    {
                        WriteElementEpm(writer, subSegment, epmValueCache, resourceType, metadata, ref alreadyDeclaredPrefix);
                    }
                }
            }

            // Close the element
            writer.WriteEndElement();
        }
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="stream">The stream to write to.</param>
        /// <param name="writerSettings">Configuration settings for the writer to create.</param>
        /// <param name="encoding">The encoding to use for writing.</param>
        /// <param name="metadataProvider">The metadata provider to use.</param>
        /// <param name="synchronous">True if the writer is created for synchronous operation; false for asynchronous.</param>
        internal ODataAtomCollectionWriter(
            Stream stream, 
            ODataWriterSettings writerSettings, 
            Encoding encoding, 
            DataServiceMetadataProviderWrapper metadataProvider,
            bool synchronous)
            : base(writerSettings.Version, metadataProvider, synchronous)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(stream != null, "stream != null");
            Debug.Assert(writerSettings != null, "writerSettings != null");

            this.outputStream = new AsyncBufferedStream(stream);
            this.writer = ODataAtomWriterUtils.CreateXmlWriter(this.outputStream, writerSettings, encoding);
        }
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="version">The version of the OData protocol to use.</param>
        /// <param name="baseUri">The Base URI to use for all the URIs being written.</param>
        /// <param name="writingResponse">True if the writer is to write a response payload; false if it's to write a request payload.</param>
        /// <param name="metadataProvider">The metadata provider to use.</param>
        /// <param name="writingFeed">True if the writer is created for writing a feed; false when it is created for writing an entry.</param>
        /// <param name="synchronous">True if the writer is created for synchronous operation; false for asynchronous.</param>
        protected ODataWriterCore(
            ODataVersion version, 
            Uri baseUri, 
            bool writingResponse, 
            DataServiceMetadataProviderWrapper metadataProvider, 
            bool writingFeed, 
            bool synchronous)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(baseUri == null || baseUri.IsAbsoluteUri, "baseUri must be either null or absolute");

            this.version = version;
            this.baseUri = baseUri;
            this.writingResponse = writingResponse;
            this.metadataProvider = metadataProvider;
            this.writingFeed = writingFeed;
            this.synchronous = synchronous;
            this.scopes.Push(Scope.Create(WriterState.Start, null));
        }
        /// <summary>
        /// Writes property names and value pairs.
        /// </summary>
        /// <param name="jsonWriter">The <see cref="JsonWriter"/> 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="properties">The enumeration of properties to write out.</param>
        /// <param name="version">The protocol version used for writing.</param>
        internal static void WriteProperties(
            JsonWriter jsonWriter, 
            DataServiceMetadataProviderWrapper metadata, 
            ResourceType owningType, 
            IEnumerable<ODataProperty> properties, 
            ODataVersion version)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(jsonWriter != null, "jsonWriter != null");

            if (properties == null)
            {
                return;
            }

            foreach (ODataProperty property in properties)
            {
                WriteProperty(jsonWriter, metadata, property, owningType, version);
            }
        }
        /// <summary>
        /// Given a target segment the method returns the text value of the property mapped to that segment to be used in EPM.
        /// </summary>
        /// <param name="targetSegment">The target segment to read the value for.</param>
        /// <param name="epmValueCache">The entry EPM value cache to use.</param>
        /// <param name="resourceType">The resource type of the entry being processed.</param>
        /// <param name="metadata">The metadata provider to use.</param>
        /// <returns>The test representation of the value, or the method throws if the text representation was not possible to obtain.</returns>
        private static string GetEntryPropertyValueAsText(
            EpmTargetPathSegment targetSegment, 
            EntryPropertiesValueCache epmValueCache, 
            ResourceType resourceType,
            DataServiceMetadataProviderWrapper metadata)
        {
            Debug.Assert(targetSegment != null, "targetSegment != null");
            Debug.Assert(targetSegment.HasContent, "The target segment to read property for must have content.");
            Debug.Assert(targetSegment.EpmInfo != null, "The EPM info must be available on the target segment to read its property.");
            Debug.Assert(epmValueCache != null, "epmValueCache != null");
            Debug.Assert(resourceType != null, "resourceType != null");

            bool nullOnParentProperty;
            object propertyValue = targetSegment.EpmInfo.ReadEntryPropertyValue(epmValueCache, resourceType, metadata, out nullOnParentProperty);
            return EpmWriterUtils.GetPropertyValueAsText(propertyValue);
        }
        /// <summary>
        /// Given a target segment the method returns the text value of the property mapped to that segment to be used in EPM.
        /// </summary>
        /// <param name="targetSegment">The target segment to read the value for.</param>
        /// <param name="epmValueCache">The entry EPM value cache to use.</param>
        /// <param name="resourceType">The resource type of the entry being processed.</param>
        /// <param name="metadata">The metadata provider to use.</param>
        /// <returns>The test representation of the value, or the method throws if the text representation was not possible to obtain.</returns>
        private static string GetEntryPropertyValueAsText(
            EpmTargetPathSegment targetSegment, 
            EntryPropertiesValueCache epmValueCache, 
            ResourceType resourceType,
            DataServiceMetadataProviderWrapper metadata)
        {
            Debug.Assert(targetSegment != null, "targetSegment != null");
            Debug.Assert(targetSegment.HasContent, "The target segment to read property for must have content.");
            Debug.Assert(targetSegment.EpmInfo != null, "The EPM info must be available on the target segment to read its property.");
            Debug.Assert(epmValueCache != null, "epmValueCache != null");
            Debug.Assert(resourceType != null, "resourceType != null");

            bool nullOnParentProperty;
            object propertyValue = targetSegment.EpmInfo.ReadEntryPropertyValue(epmValueCache, resourceType, metadata, out nullOnParentProperty);
            if (propertyValue == null)
            {
                // TODO: In V3 when we use new format for null values using the m:null attribute we need to check here
                // if we're mapping into an attribute, in which case we should fail (can't write null to attribute in V3)
                // or if we're writing into element return true null, to use the m:null attribute.
                // In V2 nulls are written out as empty string always (and they're written into content as well)
                return string.Empty;
            }
            else
            {
                return EpmWriterUtils.GetPropertyValueAsText(propertyValue);
            }
        }
        /// <summary>
        /// Writes an EPM attribute target.
        /// </summary>
        /// <param name="writer">The writer to write to.</param>
        /// <param name="targetSegment">The target segment describing the attribute to write.</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="alreadyDeclaredPrefix">The name of the prefix if it was already declared.</param>
        private static void WriteAttributeEpm(
            XmlWriter writer, 
            EpmTargetPathSegment targetSegment, 
            EntryPropertiesValueCache epmValueCache, 
            ResourceType resourceType, 
            DataServiceMetadataProviderWrapper metadata,
            ref string alreadyDeclaredPrefix)
        {
            Debug.Assert(writer != null, "writer != null");
            Debug.Assert(targetSegment != null && targetSegment.IsAttribute, "Only attribute target segments are supported by this method.");
            Debug.Assert(targetSegment.HasContent, "Attribute target segments must have content.");

            string textPropertyValue = GetEntryPropertyValueAsText(targetSegment, epmValueCache, resourceType, metadata);
            Debug.Assert(textPropertyValue != null, "Text value of a property mapped to attribute must not be null, the GetEntryPropertyValueAsText should take care of that.");

            // If the prefix is null, the WCF DS will still write it as the default namespace, so we need it to be an empty string.
            string attributePrefix = targetSegment.SegmentNamespacePrefix ?? string.Empty;
            writer.WriteAttributeString(attributePrefix, targetSegment.AttributeName, targetSegment.SegmentNamespaceUri, textPropertyValue);

            // Write out the declaration explicitely only if the prefix is not empty (just like the WCF DS does)
            if (attributePrefix.Length > 0)
            {
                WriteNamespaceDeclaration(writer, targetSegment, ref alreadyDeclaredPrefix);
            }
        }
        /// <summary>
        /// Write an <see cref="ODataProperty" /> to the given stream. This method creates an
        /// async buffered stream and writes the property to it.
        /// </summary>
        /// <param name="jsonWriter">The <see cref="JsonWriter"/> 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.</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="writingResponse">Flag indicating whether a request or a response is being written.</param>
        internal static void WriteTopLevelProperty(
            JsonWriter jsonWriter, 
            DataServiceMetadataProviderWrapper metadata, 
            ODataProperty property, 
            ResourceType owningType,
            ODataVersion version, 
            bool writingResponse)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(jsonWriter != null, "jsonWriter != null");
            Debug.Assert(property != null, "property != null");

            ODataJsonWriterUtils.WriteDataWrapper(
                jsonWriter,
                writingResponse,
                () =>
                {
                    jsonWriter.StartObjectScope();
                    WriteProperty(jsonWriter, metadata, property, owningType, version);
                    jsonWriter.EndObjectScope();
                });
        }
        /// <summary>
        /// Writes a name/value pair for a property.
        /// </summary>
        /// <param name="jsonWriter">The <see cref="JsonWriter"/> 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>
        private static void WriteProperty(
            JsonWriter jsonWriter, 
            DataServiceMetadataProviderWrapper metadata, 
            ODataProperty property, 
            ResourceType owningType,
            ODataVersion version)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(jsonWriter != null, "jsonWriter != null");

            ValidationUtils.ValidateProperty(property);
            ResourceProperty resourceProperty = ValidationUtils.ValidatePropertyDefined(property.Name, owningType);
            bool isOpenPropertyType = owningType != null && owningType.IsOpenType && resourceProperty == null;

            jsonWriter.WriteName(property.Name);
            object value = 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));
                }

                jsonWriter.WriteValue(null);
            }
            else
            {
                ResourceType resourcePropertyType = resourceProperty == null ? null : resourceProperty.ResourceType;
                ODataComplexValue complexValue = value as ODataComplexValue;
                if (complexValue != null)
                {
                    WriteComplexValue(jsonWriter, metadata, complexValue, resourcePropertyType, isOpenPropertyType, version);
                }
                else
                {
                    ODataMultiValue multiValue = value as ODataMultiValue;
                    if (multiValue != null)
                    {
                        ODataVersionChecker.CheckMultiValueProperties(version, property.Name);
                        WriteMultiValue(jsonWriter, metadata, multiValue, resourcePropertyType, isOpenPropertyType, version);
                    }
                    else
                    {
                        WritePrimitiveValue(jsonWriter, value, resourcePropertyType);
                    }
                }
            }
        }
Beispiel #14
0
        /// <summary>
        /// Resolve a type name against the provided metadata. If no type name is given we either throw (if a type name on the value is required, e.g., on entries)
        /// or infer the type from metadata (if available).
        /// </summary>
        /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param>
        /// <param name="typeFromMetadata">The type inferred from metadata or null if no metadata is available.</param>
        /// <param name="typeName">The type name to be resolved.</param>
        /// <param name="typeKind">The expected type kind of the resolved type.</param>
        /// <param name="isOpenPropertyType">True if the type name belongs to an open property.</param>
        /// <returns>A resource type for the <paramref name="typeName"/> or null if no metadata is available.</returns>
        internal static ResourceType ResolveTypeName(DataServiceMetadataProviderWrapper metadata, ResourceType typeFromMetadata, ref string typeName, ResourceTypeKind typeKind, bool isOpenPropertyType)
        {
            DebugUtils.CheckNoExternalCallers();

            ResourceType typeFromValue = ValidationUtils.ValidateTypeName(metadata, typeName, typeKind, isOpenPropertyType);
            typeFromValue = ValidationUtils.ValidateMetadataType(typeFromMetadata, typeFromValue, typeKind);

            // derive the type name from the metadata if available
            if (typeName == null && typeFromValue != null)
            {
                typeName = typeFromValue.FullName;
            }

            return typeFromValue;
        }
        /// <summary>
        /// Creates a new ODataMessageWriter for the given request message and writer settings.
        /// </summary>
        /// <param name="requestMessage">The request message for which to create the writer.</param>
        /// <param name="settings">The writer settings to use for writing the message payload.</param>
        /// <param name="metadataProvider">The metadata provider to use.</param>
        public ODataMessageWriter(IODataRequestMessage requestMessage, ODataWriterSettings settings, IDataServiceMetadataProvider metadataProvider)
        {
            ExceptionUtils.CheckArgumentNotNull(requestMessage, "requestMessage");
            ExceptionUtils.CheckArgumentNotNull(settings, "settings");

            ODataVersionChecker.CheckVersionSupported(settings.Version);

            this.writingResponse = false;
            this.message = new ODataRequestMessage(requestMessage);
            this.settings = settings;
            if (metadataProvider != null)
            {
                this.metadataProvider = new DataServiceMetadataProviderWrapper(metadataProvider);
            }
        }
        /// <summary>
        /// Writes a service document in ATOM/XML 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="defaultWorkspace">The default workspace to write in the service document.</param>
        /// <param name="baseUri">The base Uri specified in the writer settings for writing the service document.</param>
        internal static void WriteServiceDocument(
            XmlWriter writer,
            DataServiceMetadataProviderWrapper metadata,
            ODataWorkspace defaultWorkspace,
            Uri baseUri)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(writer != null, "writer != null");
            Debug.Assert(defaultWorkspace != null, "defaultWorkspace != null");

            if (baseUri == null)
            {
                // We require a base Uri for writing service documents in Xml format since the Uris for resource collections/entity sets
                // will be relative.
                throw new ODataException(Strings.ODataAtomWriterUtils_BaseUriRequiredForWritingServiceDocument);
            }

            IEnumerable<ODataResourceCollectionInfo> collections =
                ValidationUtils.ValidateWorkspace(metadata == null ? null : metadata.ResourceSets, defaultWorkspace);

            // <app:service>
            writer.WriteStartElement(string.Empty, AtomConstants.AtomPublishingServiceElementName, AtomConstants.AtomPublishingNamespace);

            // xml:base=...
            writer.WriteAttributeString(AtomConstants.XmlBaseAttributeName, AtomConstants.XmlNamespace, baseUri.AbsoluteUri);

            // xmlns=http://www.w3.org/2007/app
            writer.WriteAttributeString(AtomConstants.XmlnsNamespacePrefix, AtomConstants.XmlNamespacesNamespace, AtomConstants.AtomPublishingNamespace);

            // xmlns:atom="http://www.w3.org/2005/Atom"
            writer.WriteAttributeString(
                AtomConstants.NonEmptyAtomNamespacePrefix,
                AtomConstants.XmlNamespacesNamespace,
                AtomConstants.AtomNamespace);

            // <app:workspace>
            writer.WriteStartElement(string.Empty, AtomConstants.AtomPublishingWorkspaceElementName, AtomConstants.AtomPublishingNamespace);

            ODataAtomWriterMetadataUtils.WriteWorkspaceMetadata(writer, defaultWorkspace);

            foreach (ODataResourceCollectionInfo collectionInfo in collections)
            {
                // <app:collection>
                writer.WriteStartElement(string.Empty, AtomConstants.AtomPublishingCollectionElementName, AtomConstants.AtomPublishingNamespace);

                // The name of the collection is the entity set name; The href of the <app:collection> element must be the link for the entity set.
                // Since we model the collection as having a 'Name' (for JSON) we require a base Uri for Atom/Xml.
                writer.WriteAttributeString(AtomConstants.AtomHRefAttributeName, Uri.EscapeUriString(collectionInfo.Name));

                ODataAtomWriterMetadataUtils.WriteCollectionMetadata(writer, collectionInfo);

                // </app:collection>
                writer.WriteEndElement();
            }

            // </app:workspace>
            writer.WriteEndElement();

            // </app:service>
            writer.WriteEndElement();
        }
        /// <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();
        }
		/// <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>
		/// 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>
		/// Reads a property value starting on an entry.
		/// </summary>
		/// <param name="epmValueCache">The EPM value cache for the entry to read from.</param>
		/// <param name="resourceType">The resource type of the entry.</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>
		internal object ReadEntryPropertyValue(
			EntryPropertiesValueCache epmValueCache, 
			ResourceType resourceType,
			DataServiceMetadataProviderWrapper metadata,
			out bool nullOnParentProperty)
		{
			DebugUtils.CheckNoExternalCallers();
			Debug.Assert(this.propertyValuePath != null, "The propertyValuePath should have been initialized by now.");
			Debug.Assert(this.propertyValuePath.Length > 0, "The propertyValuePath must not be empty for an entry property.");
			Debug.Assert(resourceType != null, "resourceType != null");

			// TODO - verify that we actually need the internal property PropertyValuePath
			// TODO - It might be possible to avoid the "value" type checks below if we do property value validation based on the resource type
			return this.ReadPropertyValue(
				epmValueCache.EntryProperties,
				0,
				resourceType,
				metadata,
				epmValueCache,
				out nullOnParentProperty);
		}
        /// <summary>
        /// Writes EPM value to a person construct (author or contributor).
        /// </summary>
        /// <param name="targetSegment">The target segment which points to either author or contributor element.</param>
        /// <param name="epmValueCache">The EPM value cache to use to get property values.</param>
        /// <param name="resourceType">The resource type of the entry being processed.</param>
        /// <param name="metadata">The metadata provider to use.</param>
        /// <returns>The person metadata or null if no person metadata should be written for this mapping.</returns>
        private static AtomPersonMetadata WritePersonEpm(
            EpmTargetPathSegment targetSegment, 
            EntryPropertiesValueCache epmValueCache, 
            ResourceType resourceType,
            DataServiceMetadataProviderWrapper metadata)
        {
            Debug.Assert(targetSegment != null, "targetSegment != null");
            Debug.Assert(
                targetSegment.SegmentName == AtomConstants.AtomAuthorElementName || targetSegment.SegmentName == AtomConstants.AtomContributorElementName, 
                "targetSegment must be author or contributor.");

            AtomPersonMetadata personMetadata = null;
            foreach (EpmTargetPathSegment subSegment in targetSegment.SubSegments)
            {
                Debug.Assert(subSegment.HasContent, "sub segment of author segment must have content, there are no subsegments which don't have content under author.");
                string textPropertyValue = GetEntryPropertyValueAsText(subSegment, epmValueCache, resourceType, metadata);

                if (textPropertyValue == null)
                {
                    // TODO: In Multi-Values or in V3 mapping nulls to author/contributor subelements is not legal since there's no way to express it.
                    // author/contributor subelements don't allow extension attributes, so we can't add the m:null attribute.
                    continue;
                }

                // Initialize the person element only if we actually need to write something to it.
                if (personMetadata == null)
                {
                    personMetadata = new AtomPersonMetadata();
                }

                Debug.Assert(subSegment.EpmInfo != null && subSegment.EpmInfo.Attribute != null, "The author subsegment must have EPM info and EPM attribute.");
                switch (subSegment.EpmInfo.Attribute.TargetSyndicationItem)
                {
                    case SyndicationItemProperty.AuthorName:
                    case SyndicationItemProperty.ContributorName:
                        personMetadata.Name = textPropertyValue;
                        break;
                    case SyndicationItemProperty.AuthorEmail:
                    case SyndicationItemProperty.ContributorEmail:
                        // TODO: Validate the email value. In V3 or in multi-values the email value must not be null and it must not be empty
                        // since the syndication API doesn't allow empty email values (see below) and it's questionable if ATOM allows it itself.

                        // In case the value is empty the syndication API will actually omit the email element from the payload
                        // we have to simulate that behavior here by not setting the property in that case.
                        if (textPropertyValue.Length > 0)
                        {
                            personMetadata.Email = textPropertyValue;
                        }

                        break;
                    case SyndicationItemProperty.AuthorUri:
                    case SyndicationItemProperty.ContributorUri:
                        // TODO: Validate the uri value. In V3 or in multi-values the uri value must not be null and it must not be empty
                        // since the syndication API doesn't allow empty uri values (see below) and it's questionable if ATOM allows it itself.

                        // In case the value is empty the syndication API will actually omit the uri element from the payload
                        // we have to simulate that behavior here by not setting the property in that case.
                        if (textPropertyValue.Length > 0)
                        {
                            personMetadata.UriFromEpm = textPropertyValue;
                        }

                        break;
                    default:
                        throw new ODataException(Strings.General_InternalError(InternalErrorCodes.EpmSyndicationWriter_WritePersonEpm));
                }
            }

            return personMetadata;
        }
Beispiel #22
0
        /// <summary>
        /// Creates an <see cref="ODataWriter"/> for the specified message and its stream.
        /// </summary>
        /// <param name="format">The serialization format to create the writer for.</param>
        /// <param name="encoding">The encoding to create the writer with.</param>
        /// <param name="stream">The response stream to write to.</param>
        /// <param name="settings">Writer settings to use.</param>
        /// <param name="writingResponse">True if we are writing a response message; false for request messages.</param>
        /// <param name="metadataProvider">The metadata provider to use.</param>
        /// <param name="writingFeed">True when creating a writer to write a feed; false when creating a writer to write an entry.</param>
        /// <param name="synchronous">True if the writer is created for synchronous operation; false for asynchronous.</param>
        /// <returns>The newly created <see cref="ODataWriter"/> instance.</returns>
        /// <remarks>This is used to create the writer once we've obtained the stream from the response.</remarks>
        private static ODataWriter CreateWriter(
            ODataFormat format, 
            Encoding encoding,
            Stream stream, 
            ODataWriterSettings settings, 
            bool writingResponse,
            DataServiceMetadataProviderWrapper metadataProvider,
            bool writingFeed,
            bool synchronous)
        {
            if (settings.BaseUri != null && !settings.BaseUri.IsAbsoluteUri)
            {
                throw new ODataException(Strings.ODataWriter_BaseUriMustBeNullOrAbsolute(UriUtils.UriToString(settings.BaseUri)));
            }

            switch (format)
            {
                case ODataFormat.Json:
                    return new ODataJsonWriter(stream, settings, encoding, writingResponse, metadataProvider, writingFeed, synchronous);
                case ODataFormat.Atom:
                    return new ODataAtomWriter(stream, settings, encoding, writingResponse, metadataProvider, writingFeed, synchronous);
                case ODataFormat.Default:
                    Debug.Assert(false, "Should never get here as content-type negotiation should not return Default format for entry or feed.");
                    throw new ODataException(Strings.ODataWriter_CannotCreateWriterForFormat(format.ToString()));
                default:
                    throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriter_CreateWriter_UnreachableCodePath));
            }
        }
        /// <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);
            }
        }
        /// <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;
        }
        /// <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);
        }
        /// <summary>
        /// Writes out the value of a complex property.
        /// </summary>
        /// <param name="jsonWriter">The <see cref="JsonWriter"/> 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="resourcePropertyType">The metadata type for the complex value.</param>
        /// <param name="isOpenPropertyType">True if the type name belongs to an open property.</param>
        /// <param name="version">The protocol version used for writing.</param>
        internal static void WriteComplexValue(
            JsonWriter jsonWriter,
            DataServiceMetadataProviderWrapper metadata,
            ODataComplexValue complexValue,
            ResourceType resourcePropertyType,
            bool isOpenPropertyType,
            ODataVersion version)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(complexValue != null, "complexValue != null");

            // Start the object scope which will represent the entire complex instance
            jsonWriter.StartObjectScope();

            // Write the "__metadata" : { "type": "typename" }
            // But only if we actually have a typename to write, otherwise we need the __metadata to be omitted entirely
            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, resourcePropertyType, ref typeName, ResourceTypeKind.ComplexType, isOpenPropertyType);

            if (typeName != null)
            {
                // Write the __metadata object
                jsonWriter.WriteName(JsonConstants.ODataMetadataName);
                jsonWriter.StartObjectScope();

                // "type": "typename"
                jsonWriter.WriteName(JsonConstants.ODataMetadataTypeName);
                jsonWriter.WriteValue(typeName);

                // End the __metadata
                jsonWriter.EndObjectScope();
            }

            // Write the properties of the complex value as usual
            WriteProperties(jsonWriter, metadata, complexValueType, complexValue.Properties, version);

            // End the object scope which represents the complex instance
            jsonWriter.EndObjectScope();
        }
        /// <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();
                }
            }
        }
        /// <summary>
        /// Writes a service document in JSON format.
        /// </summary>
        /// <param name="jsonWriter">The <see cref="JsonWriter"/> to write to.</param>
        /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param>
        /// <param name="defaultWorkspace">The default workspace to write in the service document.</param>
        internal static void WriteServiceDocument(
            JsonWriter jsonWriter,
            DataServiceMetadataProviderWrapper metadata,
            ODataWorkspace defaultWorkspace)
        {
            DebugUtils.CheckNoExternalCallers();
            Debug.Assert(jsonWriter != null, "jsonWriter != null");
            Debug.Assert(defaultWorkspace != null, "defaultWorkspace != null");

            IEnumerable<ODataResourceCollectionInfo> collections =
                ValidationUtils.ValidateWorkspace(metadata == null ? null : metadata.ResourceSets, defaultWorkspace);
            Debug.Assert(collections != null, "collections != null");

            WriteDataWrapper(
                jsonWriter,
                true,
                () =>
                {
                    // "{"
                    jsonWriter.StartObjectScope();

                    // "EntitySets":
                    jsonWriter.WriteName(JsonConstants.ODataServiceDocumentEntitySetsName);

                    // "["
                    jsonWriter.StartArrayScope();

                    foreach (ODataResourceCollectionInfo collectionInfo in collections)
                    {
                        ValidationUtils.ValidateResourceCollectionInfo(collectionInfo);

                        // <collection name>
                        jsonWriter.WriteValue(collectionInfo.Name);
                    }

                    // "]"
                    jsonWriter.EndArrayScope();

                    // "}"
                    jsonWriter.EndObjectScope();
                });
        }
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="metadataProvider">The metadata provider to use for binding.</param>
        public MetadataBinder(IDataServiceMetadataProvider metadataProvider)
        {
            ExceptionUtils.CheckArgumentNotNull(metadataProvider, "metadataProvider");

            this.metadataProvider = new DataServiceMetadataProviderWrapper(metadataProvider);
        }
        /// <summary>
        /// Writes out the value of a MultiValue property.
        /// </summary>
        /// <param name="jsonWriter">The <see cref="JsonWriter"/> to write to.</param>
        /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param>
        /// <param name="multiValue">The bag value to write.</param>
        /// <param name="metadataType">The metadata type for the MultiValue.</param>
        /// <param name="isOpenPropertyType">True if the type name belongs to an open property.</param>
        /// <param name="version">The protocol version used for writing.</param>
        private static void WriteMultiValue(
            JsonWriter jsonWriter, 
            DataServiceMetadataProviderWrapper metadata, 
            ODataMultiValue multiValue, 
            ResourceType metadataType,
            bool isOpenPropertyType,
            ODataVersion version)
        {
            Debug.Assert(multiValue != null, "multiValue != null");

            // Start the object scope which will represent the entire MultiValue instance
            jsonWriter.StartObjectScope();

            // "__metadata": { "type": "typename" }
            // If the MultiValue has type information write out the metadata and the type in it.
            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
            ResourceType multiValueType = MetadataUtils.ResolveTypeName(metadata, metadataType, ref typeName, ResourceTypeKind.MultiValue, isOpenPropertyType);

            if (typeName != null)
            {
                // Write the __metadata object
                jsonWriter.WriteName(JsonConstants.ODataMetadataName);
                jsonWriter.StartObjectScope();

                // "type": "typename"
                jsonWriter.WriteName(JsonConstants.ODataMetadataTypeName);
                jsonWriter.WriteValue(typeName);

                // End the __metadata
                jsonWriter.EndObjectScope();
            }

            // "results": [
            // This represents the array of items in the MultiValue
            jsonWriter.WriteDataArrayName();
            jsonWriter.StartArrayScope();

            // Iterate through the MultiValue items and write them out (treat null Items as an empty enumeration)
            IEnumerable items = multiValue.Items;
            if (items != null)
            {
                ResourceType expectedItemType = multiValueType == null ? null : ((MultiValueResourceType)multiValueType).ItemType;

                foreach (object item in items)
                {
                    ValidationUtils.ValidateMultiValueItem(item);

                    ODataComplexValue itemAsComplexValue = item as ODataComplexValue;
                    if (itemAsComplexValue != null)
                    {
                        WriteComplexValue(jsonWriter, metadata, itemAsComplexValue, expectedItemType, false, version);
                    }
                    else
                    {
                        ODataMultiValue itemAsMultiValue = item as ODataMultiValue;
                        if (itemAsMultiValue != null)
                        {
                            throw new ODataException(Strings.ODataWriter_NestedMultiValuesAreNotSupported);
                        }
                        else
                        {
                            WritePrimitiveValue(jsonWriter, item, expectedItemType);
                        }
                    }
                }
            }

            // End the array scope which holds the items
            jsonWriter.EndArrayScope();

            // End the object scope which holds the entire MultiValue
            jsonWriter.EndObjectScope();
        }