/// <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> /// 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> /// 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 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> /// 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> /// 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> /// 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> /// 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> /// 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 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); }
/// <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 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; }
/// <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> /// Finish writing an entry. /// </summary> /// <param name="entry">The entry to write.</param> protected override void EndEntry(ODataEntry entry) { Debug.Assert(entry != null, "entry != null"); string typeName = entry.TypeName; ResourceType entryType = ValidationUtils.ValidateTypeName(this.MetadataProvider, typeName, ResourceTypeKind.EntityType, false); // Initialize the property value cache and cache the entry properties. EntryPropertiesValueCache propertyValueCache = new EntryPropertiesValueCache(entry); EpmResourceTypeAnnotation epmResourceTypeAnnotation = null; if (entryType != null) { Debug.Assert(entryType.IsReadOnly, "The resource must be read-only to be applied to an entry."); entryType.EnsureEpmAvailability(); epmResourceTypeAnnotation = entryType.Epm(); } // <atom:id>idValue</atom:id> // NOTE: do not generate a relative Uri for the ID; it is independent of xml:base ODataAtomWriterUtils.WriteElementWithTextContent( this.writer, AtomConstants.AtomNamespacePrefix, AtomConstants.AtomIdElementName, AtomConstants.AtomNamespace, entry.Id); // <category term="type" scheme="odatascheme"/> // If no type information is provided, don't include the category element for type at all // NOTE: the validation of the type name happened at the beginning of this method. if (typeName != null) { ODataAtomWriterMetadataUtils.WriteCategory(this.writer, typeName, AtomConstants.ODataSchemeNamespace, null); } Uri editLink = entry.EditLink; if (editLink != null) { // <link rel="edit" title="Title" href="LinkHRef"/> ODataAtomWriterUtils.WriteReadOrEditLink(this.writer, this.BaseUri, editLink, AtomConstants.AtomEditRelationAttributeValue, typeName); } Uri readLink = entry.ReadLink; if (readLink != null) { // <link rel="self" title="Title" href="LinkHRef"/> ODataAtomWriterUtils.WriteReadOrEditLink(this.writer, this.BaseUri, readLink, AtomConstants.AtomSelfRelationAttributeValue, typeName); } // named streams IEnumerable<ODataMediaResource> namedStreams = entry.NamedStreams; if (namedStreams != null) { foreach (ODataMediaResource namedStream in namedStreams) { ValidationUtils.ValidateNamedStream(namedStream, this.Version); ODataAtomWriterUtils.WriteNamedStream(this.writer, this.BaseUri, namedStream); } } // association links IEnumerable<ODataAssociationLink> associationLinks = entry.AssociationLinks; if (associationLinks != null) { foreach (ODataAssociationLink associationLink in associationLinks) { ValidationUtils.ValidateAssociationLink(associationLink, this.Version); ODataAtomWriterUtils.WriteAssociationLink(this.writer, this.BaseUri, entry, associationLink); } } // write entry metadata including syndication EPM AtomEntryMetadata epmEntryMetadata = null; if (epmResourceTypeAnnotation != null) { ODataVersionChecker.CheckEntityPropertyMapping(this.Version, entryType); epmEntryMetadata = EpmSyndicationWriter.WriteEntryEpm( epmResourceTypeAnnotation.EpmTargetTree, propertyValueCache, entryType, this.MetadataProvider, this.Version); } ODataAtomWriterMetadataUtils.WriteEntryMetadata(this.writer, this.BaseUri, entry, epmEntryMetadata); // write the content this.WriteEntryContent(entry, entryType, propertyValueCache, epmResourceTypeAnnotation == null ? null : epmResourceTypeAnnotation.EpmSourceTree.Root); // write custom EPM if (epmResourceTypeAnnotation != null) { EpmCustomWriter.WriteEntryEpm( this.writer, epmResourceTypeAnnotation.EpmTargetTree, propertyValueCache, entryType, this.MetadataProvider); } // </entry> this.writer.WriteEndElement(); }
/// <summary> /// Finish writing an entry. /// </summary> /// <param name="entry">The entry to write.</param> protected override void EndEntry(ODataEntry entry) { Debug.Assert(entry != null, "entry != null"); string typeName = entry.TypeName; ResourceType entryType = ValidationUtils.ValidateTypeName(this.MetadataProvider, typeName, ResourceTypeKind.EntityType, false); // Initialize the property value cache and cache the entry properties. EntryPropertiesValueCache propertyValueCache = new EntryPropertiesValueCache(entry); EpmResourceTypeAnnotation epmResourceTypeAnnotation = null; if (entryType != null) { Debug.Assert(entryType.IsReadOnly, "The resource must be read-only to be applied to an entry."); entryType.EnsureEpmAvailability(); epmResourceTypeAnnotation = entryType.Epm(); } // <atom:id>idValue</atom:id> // NOTE: do not generate a relative Uri for the ID; it is independent of xml:base ODataAtomWriterUtils.WriteElementWithTextContent( this.writer, AtomConstants.AtomNamespacePrefix, AtomConstants.AtomIdElementName, AtomConstants.AtomNamespace, entry.Id); // <category term="type" scheme="odatascheme"/> // If no type information is provided, don't include the category element for type at all // NOTE: the validation of the type name happened at the beginning of this method. if (typeName != null) { ODataAtomWriterMetadataUtils.WriteCategory(this.writer, typeName, AtomConstants.ODataSchemeNamespace, null); } Uri editLink = entry.EditLink; if (editLink != null) { // <link rel="edit" title="Title" href="LinkHRef"/> ODataAtomWriterUtils.WriteReadOrEditLink(this.writer, this.BaseUri, editLink, AtomConstants.AtomEditRelationAttributeValue, typeName); } Uri readLink = entry.ReadLink; if (readLink != null) { // <link rel="self" title="Title" href="LinkHRef"/> ODataAtomWriterUtils.WriteReadOrEditLink(this.writer, this.BaseUri, readLink, AtomConstants.AtomSelfRelationAttributeValue, typeName); } // named streams IEnumerable <ODataMediaResource> namedStreams = entry.NamedStreams; if (namedStreams != null) { foreach (ODataMediaResource namedStream in namedStreams) { ValidationUtils.ValidateNamedStream(namedStream, this.Version); ODataAtomWriterUtils.WriteNamedStream(this.writer, this.BaseUri, namedStream); } } // association links IEnumerable <ODataAssociationLink> associationLinks = entry.AssociationLinks; if (associationLinks != null) { foreach (ODataAssociationLink associationLink in associationLinks) { ValidationUtils.ValidateAssociationLink(associationLink, this.Version); ODataAtomWriterUtils.WriteAssociationLink(this.writer, this.BaseUri, entry, associationLink); } } // write entry metadata including syndication EPM AtomEntryMetadata epmEntryMetadata = null; if (epmResourceTypeAnnotation != null) { ODataVersionChecker.CheckEntityPropertyMapping(this.Version, entryType); epmEntryMetadata = EpmSyndicationWriter.WriteEntryEpm( epmResourceTypeAnnotation.EpmTargetTree, propertyValueCache, entryType, this.MetadataProvider, this.Version); } ODataAtomWriterMetadataUtils.WriteEntryMetadata(this.writer, this.BaseUri, entry, epmEntryMetadata); // write the content this.WriteEntryContent(entry, entryType, propertyValueCache, epmResourceTypeAnnotation == null ? null : epmResourceTypeAnnotation.EpmSourceTree.Root); // write custom EPM if (epmResourceTypeAnnotation != null) { EpmCustomWriter.WriteEntryEpm( this.writer, epmResourceTypeAnnotation.EpmTargetTree, propertyValueCache, entryType, this.MetadataProvider); } // </entry> this.writer.WriteEndElement(); }