/// <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> /// Used for creating non-root nodes in the syndication/custom trees. /// </summary> /// <param name="segmentName">Name of xml element/attribute</param> /// <param name="segmentNamespaceUri">URI of the namespace for <paramref name="segmentName"/></param> /// <param name="segmentNamespacePrefix">Namespace prefix to be used for <paramref name="segmentNamespaceUri"/></param> /// <param name="parentSegment">Reference to the parent node if this is a sub-node, useful for traversals in visitors</param> internal EpmTargetPathSegment(String segmentName, String segmentNamespaceUri, String segmentNamespacePrefix, EpmTargetPathSegment parentSegment) : this() { DebugUtils.CheckNoExternalCallers(); Debug.Assert(segmentName == null || segmentName.Length > 0, "Empty segment name is not allowed."); this.segmentName = segmentName; this.segmentNamespaceUri = segmentNamespaceUri; this.segmentNamespacePrefix = segmentNamespacePrefix; this.parentSegment = parentSegment; }
/// <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> /// Checks if mappings could potentially result in mixed content and dis-allows it. /// </summary> /// <param name="currentSegment">Segment being processed.</param> /// <param name="ancestorHasContent">Does any of the ancestors have content.</param> /// <returns>boolean indicating if the tree is valid or not.</returns> private static bool HasMixedContent(EpmTargetPathSegment currentSegment, bool ancestorHasContent) { foreach (EpmTargetPathSegment childSegment in currentSegment.SubSegments.Where(s => !s.IsAttribute)) { if (childSegment.HasContent && ancestorHasContent) { return(true); } if (HasMixedContent(childSegment, childSegment.HasContent || ancestorHasContent)) { return(true); } } return(false); }
/// <summary> /// Checks the validity of a tree. /// </summary> /// <param name="currentSegment">The segment to validate.</param> private static void DebugValidate(EpmTargetPathSegment currentSegment) { if (currentSegment.ParentSegment != null) { Debug.Assert(!currentSegment.ParentSegment.IsAttribute, "Attributes must be leaf nodes."); if (currentSegment.EpmInfo != null) { switch (currentSegment.EpmInfo.MultiValueStatus) { case EntityPropertyMappingMultiValueStatus.None: Debug.Assert( currentSegment.ParentSegment.EpmInfo == null || currentSegment.ParentSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.None, "non-multiValue property can only be a child of another non-multiValue property."); break; case EntityPropertyMappingMultiValueStatus.MultiValueProperty: Debug.Assert( currentSegment.ParentSegment.EpmInfo == null || (currentSegment.ParentSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.None && currentSegment.ParentSegment.ParentSegment == null), "MultiValue must be child of the root only."); break; case EntityPropertyMappingMultiValueStatus.MultiValueItemProperty: Debug.Assert( currentSegment.ParentSegment.EpmInfo == null || (currentSegment.ParentSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty || currentSegment.ParentSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueItemProperty), "MultiValue item must be child of multiValue or another multiValue item only."); break; } } } // Walk recursively subsegments and validate them foreach (EpmTargetPathSegment subSegment in currentSegment.SubSegments) { DebugValidate(subSegment); } }
/// <summary> /// Writes a namespace declaration attribute for the namespace required by the target segment. /// </summary> /// <param name="writer">The writer to write the declaration to.</param> /// <param name="targetSegment">The target segment to write the declaration for.</param> /// <param name="alreadyDeclaredPrefix">The name of the prefix if it was already declared.</param> private static void WriteNamespaceDeclaration( XmlWriter writer, EpmTargetPathSegment targetSegment, ref string alreadyDeclaredPrefix) { Debug.Assert(writer != null, "writer != null"); Debug.Assert(targetSegment != null, "targetSegment != null"); Debug.Assert( alreadyDeclaredPrefix == null || alreadyDeclaredPrefix == targetSegment.SegmentNamespacePrefix, "Found a subsegment with different prefix than the parent segment. The custom EPM writer is not ready to handle that."); if (alreadyDeclaredPrefix == null) { writer.WriteAttributeString( AtomConstants.XmlnsNamespacePrefix, targetSegment.SegmentNamespacePrefix, AtomConstants.XmlNamespacesNamespace, targetSegment.SegmentNamespaceUri); alreadyDeclaredPrefix = targetSegment.SegmentNamespacePrefix; } }
internal void Add(EntityPropertyMappingInfo epmInfo) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(epmInfo != null, "epmInfo != null"); String targetPath = epmInfo.Attribute.TargetPath; String namespaceUri = epmInfo.Attribute.TargetNamespaceUri; String namespacePrefix = epmInfo.Attribute.TargetNamespacePrefix; EpmTargetPathSegment currentSegment = epmInfo.IsSyndicationMapping ? this.SyndicationRoot : this.NonSyndicationRoot; IList <EpmTargetPathSegment> activeSubSegments = currentSegment.SubSegments; Debug.Assert(!String.IsNullOrEmpty(targetPath), "Must have been validated during EntityPropertyMappingAttribute construction"); String[] targetSegments = targetPath.Split('/'); EpmTargetPathSegment foundSegment = null; EpmTargetPathSegment multiValueSegment = null; for (int i = 0; i < targetSegments.Length; i++) { String targetSegment = targetSegments[i]; if (targetSegment.Length == 0) { throw new ODataException(Strings.EpmTargetTree_InvalidTargetPath(targetPath)); } if (targetSegment[0] == '@' && i != targetSegments.Length - 1) { throw new ODataException(Strings.EpmTargetTree_AttributeInMiddle(targetSegment)); } foundSegment = activeSubSegments.SingleOrDefault( segment => segment.SegmentName == targetSegment && (epmInfo.IsSyndicationMapping || segment.SegmentNamespaceUri == namespaceUri) && segment.MatchCriteria(epmInfo.CriteriaValue, epmInfo.Criteria)); if (foundSegment != null) { currentSegment = foundSegment; if (currentSegment.EpmInfo != null && currentSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty) { multiValueSegment = currentSegment; } } else { currentSegment = new EpmTargetPathSegment(targetSegment, namespaceUri, namespacePrefix, currentSegment); currentSegment.Criteria = epmInfo.Criteria; currentSegment.CriteriaValue = epmInfo.CriteriaValue; if (targetSegment[0] == '@') { activeSubSegments.Insert(0, currentSegment); } else { activeSubSegments.Add(currentSegment); } } activeSubSegments = currentSegment.SubSegments; } // If we're adding a multiValue property to already existing segment which maps to a non-multiValue property (no EpmInfo or one pointing to a non-multiValue property) // OR if we're adding a non-multiValue property to a segment which has multiValue in its path // we need to fail, since it's invalid to have multiValue property being mapped to the same top-level element as anything else. if ((epmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty && foundSegment != null && (foundSegment.EpmInfo == null || foundSegment.EpmInfo.MultiValueStatus != EntityPropertyMappingMultiValueStatus.MultiValueProperty)) || (epmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.None && multiValueSegment != null)) { EntityPropertyMappingInfo multiValuePropertyEpmInfo = epmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty ? epmInfo : multiValueSegment.EpmInfo; // Trying to map MultiValue property to the same target as something else which was already mapped there // It is ok to map to atom:category and atom:link elements with different sources only if the criteria values are different. if (epmInfo.CriteriaValue != null) { throw new ODataException(Strings.EpmTargetTree_MultiValueAndNormalPropertyMappedToTheSameConditionalTopLevelElement( multiValuePropertyEpmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name, EpmTargetTree.GetPropertyNameFromEpmInfo(multiValuePropertyEpmInfo), epmInfo.CriteriaValue)); } else { throw new ODataException(Strings.EpmTargetTree_MultiValueAndNormalPropertyMappedToTheSameTopLevelElement( multiValuePropertyEpmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name, EpmTargetTree.GetPropertyNameFromEpmInfo(multiValuePropertyEpmInfo))); } } Debug.Assert( epmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.None || epmInfo.IsSyndicationMapping, "Custom EPM mapping is not supported for multiValue properties."); // We only allow multiValues to map to ATOM constructs which can be repeated. if (epmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueItemProperty) { Debug.Assert( epmInfo.Attribute.TargetSyndicationItem != SyndicationItemProperty.CustomProperty, "Trying to add custom mapped property to a syndication target tree."); // Right now all the SyndicationItemProperty targets which do not have SyndicationParent.Entry are valid targets for multiValues // and all the ones which have SyndicationParent.Entry (Title, Updated etc.) are not valid targets for multiValues. if (epmInfo.SyndicationParent == EpmSyndicationParent.Entry) { throw new ODataException(Strings.EpmTargetTree_MultiValueMappedToNonRepeatableAtomElement( epmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name, EpmTargetTree.GetPropertyNameFromEpmInfo(epmInfo))); } } Debug.Assert( epmInfo.MultiValueStatus != EntityPropertyMappingMultiValueStatus.MultiValueProperty || targetSegments.Length == 1, "MultiValue property itself can only be mapped to the top-level element."); if (currentSegment.EpmInfo != null) { if (currentSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty) { Debug.Assert( epmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty, "MultiValue property values can't be mapped directly to the top-level element content (no such syndication mapping exists)."); // The info we're trying to add is a multiValue property, which would mean two multiValue properties trying to map to the same top-level element. // This can happen if the base type defines mapping for a multiValue property and the derived type defines it again // in which case we will try to add the derived type mapping again. // So we need to check that these properties are from the same source // It is ok to map to atom:category and atom:link elements with different sources only if the criteria values are different. if (epmInfo.Attribute.SourcePath != currentSegment.EpmInfo.Attribute.SourcePath) { if (epmInfo.CriteriaValue != null) { throw new ODataException(Strings.EpmTargetTree_TwoMultiValuePropertiesMappedToTheSameConditionalTopLevelElement( currentSegment.EpmInfo.Attribute.SourcePath, epmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name, epmInfo.CriteriaValue)); } else { throw new ODataException(Strings.EpmTargetTree_TwoMultiValuePropertiesMappedToTheSameTopLevelElement( currentSegment.EpmInfo.Attribute.SourcePath, epmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name)); } } Debug.Assert( !foundSegment.EpmInfo.DefiningTypesAreEqual(epmInfo), "Trying to add a multiValue property mapping for the same property on the same type twice. The souce tree should have prevented this from happening."); // If the sources are the same (and the types are different), we can safely overwrite the epmInfo // with the new one (which is for the derived type) // The epm info is stored below. } else { Debug.Assert( epmInfo.MultiValueStatus != EntityPropertyMappingMultiValueStatus.MultiValueProperty, "Only non-multiValue propeties should get here, we cover the rest above."); // Two EpmAttributes with same TargetName in the inheritance hierarchy throw new ODataException(Strings.EpmTargetTree_DuplicateEpmAttrsWithSameTargetName(EpmTargetTree.GetPropertyNameFromEpmInfo(currentSegment.EpmInfo), currentSegment.EpmInfo.DefiningType.Name, currentSegment.EpmInfo.Attribute.SourcePath, epmInfo.Attribute.SourcePath)); } } // Increment the number of properties for which KeepInContent is false if (!epmInfo.Attribute.KeepInContent) { if (epmInfo.IsAtomLinkMapping || epmInfo.IsAtomCategoryMapping) { this.countOfNonContentV3mappings++; } else { this.countOfNonContentV2mappings++; } } currentSegment.EpmInfo = epmInfo; // Mixed content is dis-allowed. Since root has no ancestor, pass in false for ancestorHasContent if (EpmTargetTree.HasMixedContent(this.NonSyndicationRoot, false)) { throw new ODataException(Strings.EpmTargetTree_InvalidTargetPath(targetPath)); } }
/// <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> /// Initializes the sub-trees for syndication and non-syndication content. /// </summary> internal EpmTargetTree() { DebugUtils.CheckNoExternalCallers(); this.syndicationRoot = new EpmTargetPathSegment(); this.nonSyndicationRoot = new EpmTargetPathSegment(); }
/// <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> /// Removes a path in the tree which is obtained by looking at the EntityPropertyMappingAttribute in the <paramref name="epmInfo"/>. /// </summary> /// <param name="epmInfo">EnitityPropertyMappingInfo holding the target path</param> internal void Remove(EntityPropertyMappingInfo epmInfo) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(epmInfo != null, "epmInfo != null"); // We should never try to remove a multiValue item property from the target tree. // If derived type redefines mapping for a given multiValue, the multiValue node itself should be removed first and then all its new items // should be added (but since the multiValue node, that is their parent, was replaces, there should be no collisions and thus no need to remove anything) Debug.Assert(epmInfo.MultiValueStatus != EntityPropertyMappingMultiValueStatus.MultiValueItemProperty, "We should never try to remove a multiValue item property."); String targetName = epmInfo.Attribute.TargetPath; String namespaceUri = epmInfo.Attribute.TargetNamespaceUri; EpmTargetPathSegment currentSegment = epmInfo.IsSyndicationMapping ? this.SyndicationRoot : this.NonSyndicationRoot; List <EpmTargetPathSegment> activeSubSegments = currentSegment.SubSegments; Debug.Assert(!String.IsNullOrEmpty(targetName), "Must have been validated during EntityPropertyMappingAttribute construction"); String[] targetSegments = targetName.Split('/'); for (int i = 0; i < targetSegments.Length; i++) { String targetSegment = targetSegments[i]; Debug.Assert(targetSegment.Length > 0 && (targetSegment[0] != '@' || i == targetSegments.Length - 1), "Target segments should have been checked when adding the path to the tree"); EpmTargetPathSegment foundSegment = activeSubSegments.FirstOrDefault( segment => segment.SegmentName == targetSegment && (epmInfo.IsSyndicationMapping || segment.SegmentNamespaceUri == namespaceUri) && segment.MatchCriteria(epmInfo.CriteriaValue, epmInfo.Criteria)); if (foundSegment != null) { currentSegment = foundSegment; } else { return; } activeSubSegments = currentSegment.SubSegments; } // Recursively remove all the parent segments which will have no more children left // after removal of the current segment node if (currentSegment.EpmInfo != null) { // Since we are removing a property with KeepInContent false, we should decrement the count if (!currentSegment.EpmInfo.Attribute.KeepInContent) { if (currentSegment.EpmInfo.IsAtomLinkMapping || currentSegment.EpmInfo.IsAtomCategoryMapping) { this.countOfNonContentV3mappings--; } else { this.countOfNonContentV2mappings--; } } EpmTargetPathSegment parentSegment = null; do { // We should never be removing the multiValue property due to its children being removed Debug.Assert( currentSegment.EpmInfo == null || currentSegment.EpmInfo.MultiValueStatus != EntityPropertyMappingMultiValueStatus.MultiValueProperty || parentSegment == null, "We should never be removing the multiValue property due to its child being removed. The source tree Add method should remove the multiValue property node itself first in that case."); parentSegment = currentSegment.ParentSegment; parentSegment.SubSegments.Remove(currentSegment); currentSegment = parentSegment; }while (currentSegment.ParentSegment != null && !currentSegment.HasContent && currentSegment.SubSegments.Count == 0); } }