/// <summary> /// Determines if the cache already contains a value for the specified EPM mapping. /// </summary> /// <param name="epmInfo">The EPM info for the EPM mapping to look for.</param> /// <returns>true if the cache already contains a value for this mapping, false otherwise.</returns> internal bool Contains(EntityPropertyMappingInfo epmInfo) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(epmInfo != null, "epmInfo != null"); return(this.customEpmValues.Any(epmValue => object.ReferenceEquals(epmValue.Key, epmInfo))); }
/// <summary> /// Reads a leaf segment which maps to a property value. /// </summary> /// <param name="targetSegment">The segment being read.</param> /// <param name="entryMetadata">The ATOM entry metadata to read from.</param> private void ReadPropertyValueSegment(EpmTargetPathSegment targetSegment, AtomEntryMetadata entryMetadata) { Debug.Assert(targetSegment != null, "targetSegment != null"); Debug.Assert(entryMetadata != null, "entryMetadata != null"); 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"); switch (epmInfo.Attribute.TargetSyndicationItem) { case SyndicationItemProperty.Updated: if (this.MessageReaderSettings.ReaderBehavior.FormatBehaviorKind == ODataBehaviorKind.WcfDataServicesClient) { if (entryMetadata.UpdatedString != null) { this.SetEntryEpmValue(targetSegment.EpmInfo, entryMetadata.UpdatedString); } } else if (entryMetadata.Updated.HasValue) { this.SetEntryEpmValue(targetSegment.EpmInfo, XmlConvert.ToString(entryMetadata.Updated.Value)); } break; case SyndicationItemProperty.Published: if (this.MessageReaderSettings.ReaderBehavior.FormatBehaviorKind == ODataBehaviorKind.WcfDataServicesClient) { if (entryMetadata.PublishedString != null) { this.SetEntryEpmValue(targetSegment.EpmInfo, entryMetadata.PublishedString); } } else if (entryMetadata.Published.HasValue) { this.SetEntryEpmValue(targetSegment.EpmInfo, XmlConvert.ToString(entryMetadata.Published.Value)); } break; case SyndicationItemProperty.Rights: this.ReadTextConstructEpm(targetSegment, entryMetadata.Rights); break; case SyndicationItemProperty.Summary: this.ReadTextConstructEpm(targetSegment, entryMetadata.Summary); break; case SyndicationItemProperty.Title: this.ReadTextConstructEpm(targetSegment, entryMetadata.Title); break; default: throw new ODataException(o.Strings.General_InternalError(InternalErrorCodes.EpmSyndicationReader_ReadEntryEpm_ContentTarget)); } }
/// <summary> /// Sets the value read from EPM to a property on an entry. /// </summary> /// <param name="epmInfo">The EPM info for the mapping for which the value was read.</param> /// <param name="propertyValue">The property value read, if the value was specified as null then this should be null, /// if the value was missing the method should not be called at all. /// For primitive properties this should be the string value, for all other properties this should be the exact value type.</param> protected void SetEntryEpmValue(EntityPropertyMappingInfo epmInfo, object propertyValue) { this.SetEpmValue( this.entryState.Entry.Properties.ToReadOnlyEnumerable("Properties"), this.entryState.EntityType.ToTypeReference(), epmInfo, propertyValue); }
/// <summary> /// Adds a value to cache. /// </summary> /// <param name="epmInfo">The EPM info for the mapping according to which the value was read.</param> /// <param name="value">The value to cache.</param> /// <remarks> /// The method will only store the first value for any given EPM info, since in custom EPM /// only the first occurrence of the element/attribute is used, the others are ignored. /// </remarks> internal void Add(EntityPropertyMappingInfo epmInfo, string value) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(epmInfo != null, "epmInfo != null"); Debug.Assert(!this.Contains(epmInfo), "We should not be trying to add more than one value for any given mapping."); this.customEpmValues.Add(new KeyValuePair <EntityPropertyMappingInfo, string>(epmInfo, value)); }
private static string GetPropertyNameFromEpmInfo(EntityPropertyMappingInfo epmInfo) { if (epmInfo.Attribute.TargetSyndicationItem == SyndicationItemProperty.CustomProperty) { return epmInfo.Attribute.TargetPath; } return epmInfo.Attribute.TargetSyndicationItem.ToString(); }
/// <summary> /// Sets the value read from EPM to a property on an entry. /// </summary> /// <param name="epmInfo">The EPM info for the mapping for which the value was read.</param> /// <param name="propertyValue">The property value read, if the value was specified as null then this should be null, /// if the value was missing the method should not be called at all. /// For primitive properties this should be the string value, for all other properties this should be the exact value type.</param> protected void SetEntryEpmValue(EntityPropertyMappingInfo epmInfo, object propertyValue) { this.SetEpmValue( ReaderUtils.GetPropertiesList(this.entryState.Entry.Properties), this.entryState.EntityType.ToTypeReference(), epmInfo, propertyValue); }
private static string GetPropertyNameFromEpmInfo(EntityPropertyMappingInfo epmInfo) { if (epmInfo.Attribute.TargetSyndicationItem == SyndicationItemProperty.CustomProperty) { return epmInfo.Attribute.TargetPath; } if (!epmInfo.IsEFProvider) { return epmInfo.Attribute.TargetSyndicationItem.ToString(); } return EpmTranslate.MapSyndicationPropertyToEpmTargetPath(epmInfo.Attribute.TargetSyndicationItem); }
/// <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) { Debug.Assert(epmInfo != null, "epmInfo != null"); 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)); 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) { this.countOfNonContentV2mappings--; } EpmTargetPathSegment parentSegment = null; do { parentSegment = currentSegment.ParentSegment; parentSegment.SubSegments.Remove(currentSegment); currentSegment = parentSegment; }while (currentSegment.ParentSegment != null && !currentSegment.HasContent && currentSegment.SubSegments.Count == 0); } }
internal static EntityPropertyMappingAttribute GetEntityPropertyMapping(EpmSourcePathSegment epmSourcePathSegment) { if (epmSourcePathSegment == null) { return(null); } EntityPropertyMappingInfo epmInfo = epmSourcePathSegment.EpmInfo; if (epmInfo == null) { return(null); } return(epmInfo.Attribute); }
/// <summary> /// Given an <see cref="EntityPropertyMappingInfo"/> gives the correct target path for it /// </summary> /// <param name="epmInfo">Given <see cref="EntityPropertyMappingInfo"/></param> /// <returns>String with the correct value for the target path</returns> private static String GetPropertyNameFromEpmInfo(EntityPropertyMappingInfo epmInfo) { if (epmInfo.Attribute.TargetSyndicationItem == SyndicationItemProperty.CustomProperty) { return(epmInfo.Attribute.TargetPath); } // for EF provider we want to return a name that corresponds to attribute in the edmx file while for CLR provider // and the client we want to return a name that corresponds to the enum value used in EntityPropertyMapping attribute. return (#if ASTORIA_SERVER epmInfo.IsEFProvider ? EpmTranslate.MapSyndicationPropertyToEpmTargetPath(epmInfo.Attribute.TargetSyndicationItem) : #endif epmInfo.Attribute.TargetSyndicationItem.ToString()); }
/// <summary> /// Reads a property value starting on an entry. /// </summary> /// <param name="epmInfo">The EPM info which describes the mapping for which to read the property value.</param> /// <param name="epmValueCache">The EPM value cache for the entry to read from.</param> /// <param name="entityType">The type of the entry.</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> protected object ReadEntryPropertyValue( EntityPropertyMappingInfo epmInfo, EntryPropertiesValueCache epmValueCache, IEdmEntityTypeReference entityType) { Debug.Assert(epmInfo != null, "epmInfo != null"); Debug.Assert(epmInfo.PropertyValuePath != null, "The PropertyValuePath should have been initialized by now."); Debug.Assert(epmInfo.PropertyValuePath.Length > 0, "The PropertyValuePath must not be empty for an entry property."); Debug.Assert(entityType != null, "entityType != null"); // TODO - It might be possible to avoid the "value" type checks below if we do property value validation based on the type return(this.ReadPropertyValue( epmInfo, epmValueCache.EntryProperties, 0, entityType, epmValueCache)); }
/// <summary> /// Sets the value read from EPM to a property on an entry. /// </summary> /// <param name="targetList">The target list, which is a list of properties (on entry or complex value).</param> /// <param name="targetTypeReference">The type of the value on which to set the property (can be entity or complex).</param> /// <param name="epmInfo">The EPM info for the mapping for which the value was read.</param> /// <param name="propertyValue">The property value read, if the value was specified as null then this should be null, /// if the value was missing the method should not be called at all. /// For primitive properties this should be the string value, for all other properties this should be the exact value type.</param> protected void SetEpmValue( IList targetList, IEdmTypeReference targetTypeReference, EntityPropertyMappingInfo epmInfo, object propertyValue) { Debug.Assert(epmInfo != null, "epmInfo != null"); Debug.Assert(targetTypeReference != null, "targetTypeReference != null"); Debug.Assert(targetList != null, "targetList != null"); Debug.Assert( targetTypeReference.IsODataEntityTypeKind() || targetTypeReference.IsODataComplexTypeKind(), "Only entity and complex types can have an EPM value set on them."); this.SetEpmValueForSegment( epmInfo, 0, targetTypeReference.AsStructuredOrNull(), (List <ODataProperty>)targetList, propertyValue); }
/// <summary> /// Gets the <see cref="EntityPropertyMappingAttribute"/> for the specified <paramref name="epmSourcePathSegment"/>. /// </summary> /// <param name="epmSourcePathSegment">The EPM source path segment to get the <see cref="EntityPropertyMappingAttribute"/> from.</param> /// <returns>The <see cref="EntityPropertyMappingAttribute"/> for the specified <paramref name="epmSourcePathSegment"/> or null if none exists.</returns> internal static EntityPropertyMappingAttribute GetEntityPropertyMapping(EpmSourcePathSegment epmSourcePathSegment) { DebugUtils.CheckNoExternalCallers(); if (epmSourcePathSegment == null) { return(null); } EntityPropertyMappingInfo epmInfo = epmSourcePathSegment.EpmInfo; if (epmInfo == null) { return(null); } EntityPropertyMappingAttribute epmAttribute = epmInfo.Attribute; Debug.Assert(epmAttribute != null, "Attribute should always be initialized for EpmInfo."); return(epmAttribute); }
/// <summary> /// Reads a property value starting on a complex value. /// </summary> /// <param name="epmInfo">The EPM info which describes the mapping for which to read the property value.</param> /// <param name="complexValue">The complex value to start with.</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="complexType">The type of the complex value.</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( EntityPropertyMappingInfo epmInfo, ODataComplexValue complexValue, EpmValueCache epmValueCache, int sourceSegmentIndex, IEdmComplexTypeReference complexType) { Debug.Assert(epmInfo != null, "epmInfo != null"); Debug.Assert(epmInfo.PropertyValuePath != null, "The PropertyValuePath should have been initialized by now."); Debug.Assert(epmInfo.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(complexType != null, "complexType != null"); Debug.Assert(complexValue != null, "complexValue != null"); return(this.ReadPropertyValue( epmInfo, EpmValueCache.GetComplexValueProperties(epmValueCache, complexValue, false), sourceSegmentIndex, complexType, epmValueCache)); }
/// <summary>Compares the defining type of this info and other EpmInfo object.</summary> /// <param name="other">The other EpmInfo object to compare to.</param> /// <returns>true if the defining types are the same</returns> internal bool DefiningTypesAreEqual(EntityPropertyMappingInfo other) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(other != null, "other != null"); return this.DefiningType == other.DefiningType; }
/// <summary> /// Trims the start of the property value path by removing the path to the multivalue property. /// </summary> /// <param name="multiValueEpmInfo">Epm info for the multivalue property itself..</param> internal void TrimMultiValueItemPropertyPath(EntityPropertyMappingInfo multiValueEpmInfo) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(multiValueEpmInfo != null, "multiValueEpmInfo != null"); Debug.Assert(multiValueEpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty, "The multiValueEpmInfo must be a multiValue property info."); #if DEBUG Debug.Assert(multiValueEpmInfo.propertyValuePath.Length <= this.propertyValuePath.Length, "The prefix is longer than the actual source path."); for (int i = 0; i < multiValueEpmInfo.propertyValuePath.Length; i++) { Debug.Assert(multiValueEpmInfo.propertyValuePath[i] == this.propertyValuePath[i], "The prefix doesn't match the actual source path."); } #endif this.propertyValuePath = this.propertyValuePath.Skip(multiValueEpmInfo.propertyValuePath.Length).ToArray(); }
internal bool DefiningTypesAreEqual(EntityPropertyMappingInfo other) { return (this.DefiningType == other.DefiningType); }
internal void Add(EntityPropertyMappingInfo epmInfo) { string targetPath = epmInfo.Attribute.TargetPath; string namespaceUri = epmInfo.Attribute.TargetNamespaceUri; string targetNamespacePrefix = epmInfo.Attribute.TargetNamespacePrefix; EpmTargetPathSegment parentSegment = epmInfo.IsSyndicationMapping ? this.SyndicationRoot : this.NonSyndicationRoot; IList<EpmTargetPathSegment> subSegments = parentSegment.SubSegments; string[] strArray = targetPath.Split(new char[] { '/' }); EpmTargetPathSegment segment2 = null; for (int i = 0; i < strArray.Length; i++) { string targetSegment = strArray[i]; if (targetSegment.Length == 0) { throw new ODataException(Microsoft.Data.OData.Strings.EpmTargetTree_InvalidTargetPath_EmptySegment(targetPath)); } if ((targetSegment[0] == '@') && (i != (strArray.Length - 1))) { throw new ODataException(Microsoft.Data.OData.Strings.EpmTargetTree_AttributeInMiddle(targetSegment)); } segment2 = subSegments.SingleOrDefault<EpmTargetPathSegment>(delegate (EpmTargetPathSegment segment) { if (!(segment.SegmentName == targetSegment)) { return false; } if (!epmInfo.IsSyndicationMapping) { return segment.SegmentNamespaceUri == namespaceUri; } return true; }); if (segment2 != null) { parentSegment = segment2; } else { parentSegment = new EpmTargetPathSegment(targetSegment, namespaceUri, targetNamespacePrefix, parentSegment); if (targetSegment[0] == '@') { subSegments.Insert(0, parentSegment); } else { subSegments.Add(parentSegment); } } subSegments = parentSegment.SubSegments; } if (parentSegment.EpmInfo != null) { throw new ODataException(Microsoft.Data.OData.Strings.EpmTargetTree_DuplicateEpmAttributesWithSameTargetName(parentSegment.EpmInfo.DefiningType.ODataFullName(), GetPropertyNameFromEpmInfo(parentSegment.EpmInfo), parentSegment.EpmInfo.Attribute.SourcePath, epmInfo.Attribute.SourcePath)); } if (!epmInfo.Attribute.KeepInContent) { this.countOfNonContentV2Mappings++; } parentSegment.EpmInfo = epmInfo; List<EntityPropertyMappingAttribute> ancestorsWithContent = new List<EntityPropertyMappingAttribute>(2); if (HasMixedContent(this.NonSyndicationRoot, ancestorsWithContent)) { throw new ODataException(Microsoft.Data.OData.Strings.EpmTargetTree_InvalidTargetPath_MixedContent(ancestorsWithContent[0].TargetPath, ancestorsWithContent[1].TargetPath)); } }
internal bool DefiningTypesAreEqual(EntityPropertyMappingInfo other) { return ((IEdmType) this.DefiningType).IsEquivalentTo(((IEdmType) other.DefiningType)); }
private void SetEpmValueForSegment( EntityPropertyMappingInfo epmInfo, int propertyValuePathIndex, IEdmStructuredTypeReference segmentStructuralTypeReference, List <ODataProperty> existingProperties, object propertyValue) { Debug.Assert(epmInfo != null, "epmInfo != null"); Debug.Assert(propertyValuePathIndex < epmInfo.PropertyValuePath.Length, "The propertyValuePathIndex is out of bounds."); Debug.Assert(existingProperties != null, "existingProperties != null"); string propertyName = epmInfo.PropertyValuePath[propertyValuePathIndex].PropertyName; // Do not set out-of-content values if the EPM is defined as KeepInContent=true. if (epmInfo.Attribute.KeepInContent) { return; } // Try to find the property in the existing properties // If the property value is atomic from point of view of EPM (non-streaming collection or primitive) then if it already exists // it must have been in-content, and thus we leave it as is (note that two EPMs can't map to the same property, we verify that upfront). // If the property value is non-atomic, then it is a complex value, we might want to merge the new value comming from EPM with it. ODataProperty existingProperty = existingProperties.FirstOrDefault(p => string.CompareOrdinal(p.Name, propertyName) == 0); ODataComplexValue existingComplexValue = null; if (existingProperty != null) { // In case the property exists and it's a complex value we will try to merge. // Note that if the property is supposed to be complex, but it already has a null value, then the null wins. // Since in-content null complex value wins over any EPM complex value. existingComplexValue = existingProperty.Value as ODataComplexValue; if (existingComplexValue == null) { return; } } IEdmProperty propertyMetadata = segmentStructuralTypeReference.FindProperty(propertyName); Debug.Assert(propertyMetadata != null || segmentStructuralTypeReference.IsOpen(), "We should have verified that if the property is not declared the type must be open."); if (propertyMetadata == null && propertyValuePathIndex != epmInfo.PropertyValuePath.Length - 1) { throw new ODataException(o.Strings.EpmReader_OpenComplexOrCollectionEpmProperty(epmInfo.Attribute.SourcePath)); } // Open properties in EPM are by default of type Edm.String - there's no way to specify a typename in EPM // consumer is free to do the conversion later on if it needs to. // Note that this effectively means that ODataMessageReaderSettings.DisablePrimitiveTypeConversion is as if it's turned on for open EPM properties. IEdmTypeReference propertyType; if (propertyMetadata == null || (this.MessageReaderSettings.DisablePrimitiveTypeConversion && propertyMetadata.Type.IsODataPrimitiveTypeKind())) { propertyType = EdmCoreModel.Instance.GetString(/*nullable*/ true); } else { propertyType = propertyMetadata.Type; } // NOTE: WCF DS Server only applies the values when // - It's an open property // - It's not a key property // - It's a key property and it's a POST operation // ODataLib here will always set the property though. switch (propertyType.TypeKind()) { case EdmTypeKind.Primitive: { if (propertyType.IsStream()) { throw new ODataException(o.Strings.General_InternalError(InternalErrorCodes.EpmReader_SetEpmValueForSegment_StreamProperty)); } object primitiveValue; if (propertyValue == null) { ReaderValidationUtils.ValidateNullValue(this.atomInputContext.Model, propertyType, this.atomInputContext.MessageReaderSettings, /*validateNullValue*/ true, this.atomInputContext.Version); primitiveValue = null; } else { // Convert the value to the desired target type primitiveValue = AtomValueUtils.ConvertStringToPrimitive((string)propertyValue, propertyType.AsPrimitive()); } this.AddEpmPropertyValue(existingProperties, propertyName, primitiveValue, segmentStructuralTypeReference.IsODataEntityTypeKind()); } break; case EdmTypeKind.Complex: // Note: Unlike WCF DS we don't have a preexisting instance to override (since complex values are atomic, so we should not updated them) // In our case the complex value either doesn't exist yet on the entry being reported (easy, create it) // or it exists, but then it was created during reading of previous normal or EPM properties for this entry. It never exists before // we ever get to see the entity. So in our case we will never recreate the complex value, we always start with new one // and update it with new properties as they come. (Next time we will start over with a new complex value.) Debug.Assert( existingComplexValue == null || (existingProperty != null && existingProperty.Value == existingComplexValue), "If we have existing complex value, we must have an existing property as well."); Debug.Assert( epmInfo.PropertyValuePath.Length > propertyValuePathIndex + 1, "Complex value can not be a leaf segment in the source property path. We should have failed constructing the EPM trees for it."); if (existingComplexValue == null) { Debug.Assert(existingProperty == null, "If we don't have an existing complex value, then we must not have an existing property at all."); // Create a new complex value and set its type name to the type name of the property type (in case of EPM we never have type name from the payload) existingComplexValue = new ODataComplexValue { TypeName = propertyType.ODataFullName(), Properties = new ReadOnlyEnumerable <ODataProperty>() }; this.AddEpmPropertyValue(existingProperties, propertyName, existingComplexValue, segmentStructuralTypeReference.IsODataEntityTypeKind()); } // Get the properties list of the complex value and recursively set the next EPM segment value to it. // Note that on inner complex value we don't need to check for duplicate properties // because EPM will never add a property which already exists (see the start of this method). IEdmComplexTypeReference complexPropertyTypeReference = propertyType.AsComplex(); Debug.Assert(complexPropertyTypeReference != null, "complexPropertyTypeReference != null"); this.SetEpmValueForSegment( epmInfo, propertyValuePathIndex + 1, complexPropertyTypeReference, ReaderUtils.GetPropertiesList(existingComplexValue.Properties), propertyValue); break; case EdmTypeKind.Collection: Debug.Assert(propertyType.IsNonEntityODataCollectionTypeKind(), "Collection types in EPM must be atomic."); // In this case the property value is the internal list of items. // Create a new collection value and set the list as the list of items on it. ODataCollectionValue collectionValue = new ODataCollectionValue { TypeName = propertyType.ODataFullName(), Items = new ReadOnlyEnumerable((List <object>)propertyValue) }; this.AddEpmPropertyValue(existingProperties, propertyName, collectionValue, segmentStructuralTypeReference.IsODataEntityTypeKind()); break; default: throw new ODataException(o.Strings.General_InternalError(InternalErrorCodes.EpmReader_SetEpmValueForSegment_TypeKind)); } }
protected override void Serialize(EpmTargetPathSegment targetSegment, EpmSerializationKind kind) { if (targetSegment.HasContent) { EntityPropertyMappingInfo epmInfo = targetSegment.EpmInfo; Object propertyValue; try { propertyValue = epmInfo.PropValReader.DynamicInvoke(this.Element); } catch (System.Reflection.TargetInvocationException) { throw; } String contentType; Action <String> contentWriter; switch (epmInfo.Attribute.TargetTextContentKind) { case SyndicationTextContentKind.Html: contentType = "html"; contentWriter = this.Target.WriteString; break; case SyndicationTextContentKind.Xhtml: contentType = "xhtml"; contentWriter = this.Target.WriteRaw; break; default: contentType = "text"; contentWriter = this.Target.WriteString; break; } Action <String, bool, bool> textSyndicationWriter = (c, nonTextPossible, atomDateConstruct) => { this.Target.WriteStartElement(c, XmlConstants.AtomNamespace); if (nonTextPossible) { this.Target.WriteAttributeString(XmlConstants.AtomTypeAttributeName, String.Empty, contentType); } String textPropertyValue = propertyValue != null?ClientConvert.ToString(propertyValue, atomDateConstruct) : atomDateConstruct?ClientConvert.ToString(DateTime.MinValue, atomDateConstruct) : String.Empty; contentWriter(textPropertyValue); this.Target.WriteEndElement(); }; switch (epmInfo.Attribute.TargetSyndicationItem) { case SyndicationItemProperty.AuthorEmail: case SyndicationItemProperty.ContributorEmail: textSyndicationWriter(XmlConstants.AtomEmailElementName, false, false); break; case SyndicationItemProperty.AuthorName: case SyndicationItemProperty.ContributorName: textSyndicationWriter(XmlConstants.AtomNameElementName, false, false); this.authorNamePresent = true; break; case SyndicationItemProperty.AuthorUri: case SyndicationItemProperty.ContributorUri: textSyndicationWriter(XmlConstants.AtomUriElementName, false, false); break; case SyndicationItemProperty.Updated: textSyndicationWriter(XmlConstants.AtomUpdatedElementName, false, true); this.updatedPresent = true; break; case SyndicationItemProperty.Published: textSyndicationWriter(XmlConstants.AtomPublishedElementName, false, true); break; case SyndicationItemProperty.Rights: textSyndicationWriter(XmlConstants.AtomRightsElementName, true, false); break; case SyndicationItemProperty.Summary: textSyndicationWriter(XmlConstants.AtomSummaryElementName, true, false); break; case SyndicationItemProperty.Title: textSyndicationWriter(XmlConstants.AtomTitleElementName, true, false); break; default: Debug.Assert(false, "Unhandled SyndicationItemProperty enum value - should never get here."); break; } } else { if (targetSegment.SegmentName == XmlConstants.AtomAuthorElementName) { this.CreateAuthor(false); base.Serialize(targetSegment, kind); this.FinishAuthor(); } else if (targetSegment.SegmentName == XmlConstants.AtomContributorElementName) { this.Target.WriteStartElement(XmlConstants.AtomContributorElementName, XmlConstants.AtomNamespace); base.Serialize(targetSegment, kind); this.Target.WriteEndElement(); } else { Debug.Assert(false, "Only authors and contributors have nested elements"); } } }
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> /// Validates the specified segment which is a subsegment of a MultiValue property or the MultiValue property segment itself. /// </summary> /// <param name="multiValuePropertyInfo">Info about the MultiValue property being processed. Used for exception messages only.</param> /// <param name="multiValueSegment">The segment belonging to a MultiValue property to validate.</param> /// <param name="resourceType">The resource type of the property represented by this segment (item type for the MultiValue property itself).</param> private static void ValidateMultiValueSegment( EntityPropertyMappingInfo multiValuePropertyInfo, EpmSourcePathSegment multiValueSegment, ResourceType resourceType) { Debug.Assert( multiValuePropertyInfo != null && multiValuePropertyInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty, "multiValuePropertyName must be non-null and must be a MultiValue info."); Debug.Assert(multiValueSegment != null, "multiValueSegment != null"); Debug.Assert(resourceType != null, "resourceType != null"); Debug.Assert( multiValueSegment.EpmInfo == null || multiValueSegment.EpmInfo == multiValuePropertyInfo || multiValueSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueItemProperty, "The specified segment does not belong to a MultiValue property subtree."); if (resourceType.ResourceTypeKind == ResourceTypeKind.ComplexType) { Debug.Assert( multiValueSegment.EpmInfo == null || multiValueSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty, "EPM source segment representing a complex property of a MultiValue property must not have an EpmInfo for the property itself."); // Verify that all properties of the complex type are mapped (have respective source segments) foreach (ResourceProperty property in resourceType.Properties) { string propertyName = property.Name; string resourceTypeName = resourceType.Name; EpmSourcePathSegment subSegment = multiValueSegment.SubProperties.SingleOrDefault(e => e.PropertyName == propertyName); if (subSegment == null) { throw new ODataException(Strings.EpmSourceTree_NotAllMultiValueItemPropertiesMapped( multiValuePropertyInfo.Attribute.SourcePath, multiValuePropertyInfo.DefiningType.Name, propertyName, resourceTypeName)); } else { ResourceType propertyType = property.ResourceType; // Recursive call to verify the sub segment // Note that we don't need to check for enless loops and recursion depth because we are effectively walking the EPM source tree // which itself can't have loops in it, and can't be infinite either. So if the metadata for a MultiValue property has loops in it // we would eventually fail to find a matching segment in the source tree and throw. ValidateMultiValueSegment(multiValuePropertyInfo, subSegment, propertyType); } } } else { Debug.Assert(multiValueSegment.EpmInfo != null, "Primitive value must have EpmInfo."); if (multiValueSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty) { // MultiValue of primitive types Debug.Assert(multiValueSegment.SubProperties.Count == 1, "Exactly one subproperty should be on a node representing a MultiValue property of primitive types."); Debug.Assert( multiValueSegment.SubProperties[0].IsMultiValueItemValue && multiValueSegment.SubProperties[0].EpmInfo != null && multiValueSegment.SubProperties[0].EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueItemProperty, "The only subproperty of a collectin of primitive types should be the special item's value node."); EpmSourcePathSegment leafSegment = multiValueSegment.SubProperties[0]; if (leafSegment.EpmInfo.IsAtomLinkMapping) { if (leafSegment.EpmInfo.CriteriaValue == null) { throw new ODataException(Strings.EpmSourceTree_MultiValueOfPrimitiveMappedToLinkWithoutCriteria( multiValuePropertyInfo.Attribute.SourcePath, multiValuePropertyInfo.DefiningType.Name, leafSegment.EpmInfo.Attribute.TargetPath)); } else if (leafSegment.EpmInfo.Attribute.TargetSyndicationItem != SyndicationItemProperty.LinkHref) { throw new ODataException(Strings.EpmTargetTree_ConditionalMappingLinkHrefIsRequired( multiValuePropertyInfo.Attribute.SourcePath, multiValuePropertyInfo.DefiningType.Name, leafSegment.EpmInfo.Attribute.TargetPath)); } } else if (leafSegment.EpmInfo.IsAtomCategoryMapping) { if (leafSegment.EpmInfo.CriteriaValue == null && leafSegment.EpmInfo.Attribute.TargetSyndicationItem != SyndicationItemProperty.CategoryTerm) { throw new ODataException(Strings.EpmTargetTree_ConditionalMappingCategoryTermIsRequired( multiValuePropertyInfo.Attribute.SourcePath, multiValuePropertyInfo.DefiningType.Name, leafSegment.EpmInfo.Attribute.TargetPath)); } } } else { // MultiValue of complex types, we're on a leaf primitive property. Debug.Assert( multiValueSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueItemProperty, "Only multiValue property or multiValue item property nodes should get here."); Debug.Assert(multiValueSegment.SubProperties.Count == 0, "Primtive item property value should have no sub properties."); Debug.Assert(!multiValueSegment.IsMultiValueItemValue, "Special MultiValue item value must not be a child of a complex property."); EntityPropertyMappingInfo multiValueSegmentEpmInfo = multiValueSegment.EpmInfo; if (multiValueSegmentEpmInfo.IsAtomLinkMapping != multiValueSegmentEpmInfo.IsAtomLinkMapping || multiValueSegmentEpmInfo.IsAtomCategoryMapping != multiValueSegmentEpmInfo.IsAtomCategoryMapping || String.Compare(multiValueSegmentEpmInfo.CriteriaValue, multiValueSegmentEpmInfo.CriteriaValue, StringComparison.OrdinalIgnoreCase) != 0) { throw new ODataException(Strings.EpmSourceTree_MultiValueOfComplexTypesDifferentConditionalMapping( multiValuePropertyInfo.Attribute.SourcePath, multiValuePropertyInfo.DefiningType.Name, multiValuePropertyInfo.Attribute.TargetPath)); } } } }
/// <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); } }
/// <summary> /// Reads a property value starting with the specified index to the property value path. /// </summary> /// <param name="epmInfo">The EPM info which describes the mapping for which to read the property value.</param> /// <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="structuredTypeReference">The type of the entry or complex value the <paramref name="cachedProperties"/> enumeration belongs to.</param> /// <param name="epmValueCache">The EPM value cache to use.</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( EntityPropertyMappingInfo epmInfo, IEnumerable <ODataProperty> cachedProperties, int sourceSegmentIndex, IEdmStructuredTypeReference structuredTypeReference, EpmValueCache epmValueCache) { Debug.Assert(epmInfo != null, "epmInfo != null"); Debug.Assert(epmInfo.PropertyValuePath != null, "The PropertyValuePath should have been initialized by now."); Debug.Assert(epmInfo.PropertyValuePath.Length > sourceSegmentIndex, "The PropertyValuePath must be at least as long as the source segment index."); Debug.Assert(structuredTypeReference != null, "structuredTypeReference != null"); Debug.Assert(epmValueCache != null, "epmValueCache != null"); EpmSourcePathSegment sourceSegment = epmInfo.PropertyValuePath[sourceSegmentIndex]; string propertyName = sourceSegment.PropertyName; bool lastSegment = epmInfo.PropertyValuePath.Length == sourceSegmentIndex + 1; IEdmStructuredType structuredType = structuredTypeReference.StructuredDefinition(); IEdmProperty edmProperty = WriterValidationUtils.ValidatePropertyDefined(propertyName, structuredType); if (edmProperty != null) { // If this is the last part of the path, then it has to be a primitive or atomic collection type otherwise should be a complex type if (lastSegment) { if (!edmProperty.Type.IsODataPrimitiveTypeKind() && !edmProperty.Type.IsNonEntityODataCollectionTypeKind()) { throw new ODataException(o.Strings.EpmSourceTree_EndsWithNonPrimitiveType(propertyName)); } } else { if (edmProperty.Type.TypeKind() != EdmTypeKind.Complex) { throw new ODataException(o.Strings.EpmSourceTree_TraversalOfNonComplexType(propertyName)); } } } else { Debug.Assert( structuredType.IsOpen, "Only open types can have undeclared properties, otherwise we should have failed in the ValidatePropertyDefined method."); } ODataProperty property = cachedProperties == null ? null : cachedProperties.FirstOrDefault(p => p.Name == propertyName); if (property == null) { throw new ODataException(o.Strings.EpmSourceTree_MissingPropertyOnInstance(propertyName, structuredTypeReference.ODataFullName())); } object propertyValue = property.Value; ODataComplexValue propertyComplexValue = propertyValue as ODataComplexValue; if (lastSegment) { if (propertyValue == null) { WriterValidationUtils.ValidateNullPropertyValue(edmProperty, this.WriterBehavior, this.atomOutputContext.Model); } else { // If this property is the last one it has to be either a primitive or collection if (propertyComplexValue != null) { throw new ODataException(o.Strings.EpmSourceTree_EndsWithNonPrimitiveType(propertyName)); } else { ODataCollectionValue propertyCollectionValue = propertyValue as ODataCollectionValue; if (propertyCollectionValue != null) { // Validate the type name for the collection string typeName = propertyCollectionValue.TypeName; WriterValidationUtils.ResolveTypeNameForWriting( this.atomOutputContext.Model, edmProperty == null ? null : edmProperty.Type, ref typeName, EdmTypeKind.Collection, edmProperty == null); } else { if (propertyValue is ODataStreamReferenceValue) { // Stream properties should not come here, if it were an ODataEntry property it would have been // filtered in ReadEntryPropertyValue() by "epmValueCache.EntryProperties" call. throw new ODataException(o.Strings.ODataWriter_StreamPropertiesMustBePropertiesOfODataEntry(propertyName)); } else if (propertyValue is ISpatial) { throw new ODataException(o.Strings.EpmSourceTree_OpenPropertySpatialTypeCannotBeMapped(propertyName, epmInfo.DefiningType.FullName())); } else if (edmProperty != null) { ValidationUtils.ValidateIsExpectedPrimitiveType(propertyValue, edmProperty.Type); } } } } return(propertyValue); } // Otherwise it's in the middle and thus it must be a complex value if (propertyComplexValue == null) { if (propertyValue != null) { // It's not a complex value - fail. throw new ODataException(o.Strings.EpmSourceTree_TraversalOfNonComplexType(propertyName)); } else { // The value of the property is null, which can be a null complex value // Note that we must not attempt to resolve the type as if the type name was null here, because // 1) We don't need the type for anything anyway (the value is null, this is the end) // 2) If the property is open, trying to resolve a null type name would throw // but we don't have a null type name, we have a null entire value. return(null); } } string localTypeName = propertyComplexValue.TypeName; IEdmComplexTypeReference complexValueType = WriterValidationUtils.ResolveTypeNameForWriting( this.atomOutputContext.Model, edmProperty == null ? null : edmProperty.Type, ref localTypeName, EdmTypeKind.Complex, edmProperty == null).AsComplexOrNull(); return(this.ReadComplexPropertyValue( epmInfo, propertyComplexValue, epmValueCache, sourceSegmentIndex + 1, complexValueType)); }
/// <summary> /// Override of the base Visitor method, which actually performs mapping search and serialization /// </summary> /// <param name="targetSegment">Current segment being checked for mapping</param> /// <param name="kind">Which sub segments to serialize</param> protected override void Serialize(EpmTargetPathSegment targetSegment, EpmSerializationKind kind) { if (targetSegment.HasContent) { EntityPropertyMappingInfo epmInfo = targetSegment.EpmInfo; Object propertyValue; try { propertyValue = epmInfo.ReadPropertyValue(this.Element); } catch (System.Reflection.TargetInvocationException) { throw; } String contentType; Action <String> contentWriter; switch (epmInfo.Attribute.TargetTextContentKind) { case SyndicationTextContentKind.Html: contentType = "html"; contentWriter = this.Target.WriteString; break; case SyndicationTextContentKind.Xhtml: contentType = "xhtml"; contentWriter = this.Target.WriteRaw; break; default: contentType = "text"; contentWriter = this.Target.WriteString; break; } Action <String, bool, bool> textSyndicationWriter = (c, nonTextPossible, atomDateConstruct) => { this.Target.WriteStartElement(c, XmlConstants.AtomNamespace); if (nonTextPossible) { this.Target.WriteAttributeString(XmlConstants.AtomTypeAttributeName, String.Empty, contentType); } // As per atomPub spec dateConstructs must contain valid datetime. Therefore we need to fill atom:updated/atom:published // field with something (e.g. DateTimeOffset.MinValue) if the user wants to map null to either of these fields. This will // satisfy protocol requirements and Syndication API. Note that the content will still contain information saying that the // mapped property is supposed to be null - therefore the server will know that the actual value sent by the user is null. // For all other elements we can use empty string since the content is not validated. String textPropertyValue = propertyValue != null?ClientConvert.ToString(propertyValue, atomDateConstruct) : atomDateConstruct?ClientConvert.ToString(DateTime.MinValue, atomDateConstruct) : String.Empty; contentWriter(textPropertyValue); this.Target.WriteEndElement(); }; switch (epmInfo.Attribute.TargetSyndicationItem) { case SyndicationItemProperty.AuthorEmail: case SyndicationItemProperty.ContributorEmail: textSyndicationWriter(XmlConstants.AtomEmailElementName, false, false); break; case SyndicationItemProperty.AuthorName: case SyndicationItemProperty.ContributorName: textSyndicationWriter(XmlConstants.AtomNameElementName, false, false); this.authorNamePresent = true; break; case SyndicationItemProperty.AuthorUri: case SyndicationItemProperty.ContributorUri: textSyndicationWriter(XmlConstants.AtomUriElementName, false, false); break; case SyndicationItemProperty.Updated: textSyndicationWriter(XmlConstants.AtomUpdatedElementName, false, true); this.updatedPresent = true; break; case SyndicationItemProperty.Published: textSyndicationWriter(XmlConstants.AtomPublishedElementName, false, true); break; case SyndicationItemProperty.Rights: textSyndicationWriter(XmlConstants.AtomRightsElementName, true, false); break; case SyndicationItemProperty.Summary: textSyndicationWriter(XmlConstants.AtomSummaryElementName, true, false); break; case SyndicationItemProperty.Title: textSyndicationWriter(XmlConstants.AtomTitleElementName, true, false); break; default: Debug.Assert(false, "Unhandled SyndicationItemProperty enum value - should never get here."); break; } } else { if (targetSegment.SegmentName == XmlConstants.AtomAuthorElementName) { this.CreateAuthor(false); base.Serialize(targetSegment, kind); this.FinishAuthor(); } else if (targetSegment.SegmentName == XmlConstants.AtomContributorElementName) { this.Target.WriteStartElement(XmlConstants.AtomContributorElementName, XmlConstants.AtomNamespace); base.Serialize(targetSegment, kind); this.Target.WriteEndElement(); } else { Debug.Assert(false, "Only authors and contributors have nested elements"); } } }
internal void Add(EntityPropertyMappingInfo epmInfo) { Debug.Assert(epmInfo != null, "epmInfo != null"); String targetPath = epmInfo.Attribute.TargetPath; String namespaceUri = epmInfo.Attribute.TargetNamespaceUri; 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; for (int i = 0; i < targetSegments.Length; i++) { String targetSegment = targetSegments[i]; if (targetSegment.Length == 0) { throw new InvalidOperationException(Strings.EpmTargetTree_InvalidTargetPath(targetPath)); } if (targetSegment[0] == '@' && i != targetSegments.Length - 1) { throw new InvalidOperationException(Strings.EpmTargetTree_AttributeInMiddle(targetSegment)); } foundSegment = activeSubSegments.SingleOrDefault( segment => segment.SegmentName == targetSegment && (epmInfo.IsSyndicationMapping || segment.SegmentNamespaceUri == namespaceUri)); if (foundSegment != null) { currentSegment = foundSegment; } else { currentSegment = new EpmTargetPathSegment(targetSegment, namespaceUri, currentSegment); if (targetSegment[0] == '@') { activeSubSegments.Insert(0, currentSegment); } else { activeSubSegments.Add(currentSegment); } } activeSubSegments = currentSegment.SubSegments; } if (currentSegment.EpmInfo != null) { // Two EpmAttributes with same TargetName in the inheritance hierarchy throw new ArgumentException(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) { 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 InvalidOperationException(Strings.EpmTargetTree_InvalidTargetPath(targetPath)); } }
internal void Add(EntityPropertyMappingInfo epmInfo, string value) { this.customEpmValues.Add(new KeyValuePair <EntityPropertyMappingInfo, string>(epmInfo, value)); }
protected void SetEpmValue(IList targetList, IEdmTypeReference targetTypeReference, EntityPropertyMappingInfo epmInfo, object propertyValue) { this.SetEpmValueForSegment(epmInfo, 0, targetTypeReference.AsStructuredOrNull(), (List <ODataProperty>)targetList, propertyValue); }
internal bool Contains(EntityPropertyMappingInfo epmInfo) { return(this.customEpmValues.Any <KeyValuePair <EntityPropertyMappingInfo, string> >(epmValue => object.ReferenceEquals(epmValue.Key, epmInfo))); }
protected object ReadEntryPropertyValue(EntityPropertyMappingInfo epmInfo, EntryPropertiesValueCache epmValueCache, IEdmEntityTypeReference entityType) { return(this.ReadPropertyValue(epmInfo, epmValueCache.EntryProperties, 0, entityType, epmValueCache)); }
private AtomEntryMetadata WriteEntryEpm(EntryPropertiesValueCache epmValueCache, IEdmEntityTypeReference entityType) { EpmTargetPathSegment syndicationRoot = this.epmTargetTree.SyndicationRoot; if (syndicationRoot.SubSegments.Count == 0) { return(null); } foreach (EpmTargetPathSegment segment2 in syndicationRoot.SubSegments) { if (!segment2.HasContent) { goto Label_018C; } EntityPropertyMappingInfo epmInfo = segment2.EpmInfo; object propertyValue = base.ReadEntryPropertyValue(epmInfo, epmValueCache, entityType); string propertyValueAsText = EpmWriterUtils.GetPropertyValueAsText(propertyValue); switch (epmInfo.Attribute.TargetSyndicationItem) { case SyndicationItemProperty.Updated: { if (base.WriterBehavior.FormatBehaviorKind != ODataBehaviorKind.WcfDataServicesClient) { break; } this.entryMetadata.UpdatedString = CreateDateTimeStringValue(propertyValue, base.WriterBehavior); continue; } case SyndicationItemProperty.Published: { if (base.WriterBehavior.FormatBehaviorKind != ODataBehaviorKind.WcfDataServicesClient) { goto Label_00FE; } this.entryMetadata.PublishedString = CreateDateTimeStringValue(propertyValue, base.WriterBehavior); continue; } case SyndicationItemProperty.Rights: { this.entryMetadata.Rights = CreateAtomTextConstruct(propertyValueAsText, epmInfo.Attribute.TargetTextContentKind); continue; } case SyndicationItemProperty.Summary: { this.entryMetadata.Summary = CreateAtomTextConstruct(propertyValueAsText, epmInfo.Attribute.TargetTextContentKind); continue; } case SyndicationItemProperty.Title: { this.entryMetadata.Title = CreateAtomTextConstruct(propertyValueAsText, epmInfo.Attribute.TargetTextContentKind); continue; } default: throw new ODataException(Microsoft.Data.OData.Strings.General_InternalError(InternalErrorCodes.EpmSyndicationWriter_WriteEntryEpm_ContentTarget)); } this.entryMetadata.Updated = new DateTimeOffset?(CreateDateTimeValue(propertyValue, SyndicationItemProperty.Updated, base.WriterBehavior)); continue; Label_00FE: this.entryMetadata.Published = new DateTimeOffset?(CreateDateTimeValue(propertyValue, SyndicationItemProperty.Published, base.WriterBehavior)); continue; Label_018C: this.WriteParentSegment(segment2, epmValueCache, entityType); } return(this.entryMetadata); }
internal void Remove(EntityPropertyMappingInfo epmInfo) { string targetPath = epmInfo.Attribute.TargetPath; string namespaceUri = epmInfo.Attribute.TargetNamespaceUri; EpmTargetPathSegment item = epmInfo.IsSyndicationMapping ? this.SyndicationRoot : this.NonSyndicationRoot; List<EpmTargetPathSegment> subSegments = item.SubSegments; string[] strArray = targetPath.Split(new char[] { '/' }); for (int i = 0; i < strArray.Length; i++) { string targetSegment = strArray[i]; EpmTargetPathSegment segment2 = subSegments.FirstOrDefault<EpmTargetPathSegment>(delegate (EpmTargetPathSegment segment) { if (!(segment.SegmentName == targetSegment)) { return false; } if (!epmInfo.IsSyndicationMapping) { return segment.SegmentNamespaceUri == namespaceUri; } return true; }); if (segment2 != null) { item = segment2; } else { return; } subSegments = item.SubSegments; } if (item.EpmInfo != null) { if (!item.EpmInfo.Attribute.KeepInContent) { this.countOfNonContentV2Mappings--; } EpmTargetPathSegment parentSegment = null; do { parentSegment = item.ParentSegment; parentSegment.SubSegments.Remove(item); item = parentSegment; } while (((item.ParentSegment != null) && !item.HasContent) && (item.SubSegments.Count == 0)); } }
internal void Add(EntityPropertyMappingInfo epmInfo) { DebugUtils.CheckNoExternalCallers(); List<EpmSourcePathSegment> pathToCurrentSegment = new List<EpmSourcePathSegment>(); EpmSourcePathSegment currentSourceSegment = this.Root; EpmSourcePathSegment foundSourceSegment = null; ResourceType currentType = epmInfo.ActualPropertyType; EpmSourcePathSegment multiValuePropertySegment = null; Debug.Assert(!string.IsNullOrEmpty(epmInfo.Attribute.SourcePath), "Invalid source path"); string[] propertyPath = epmInfo.Attribute.SourcePath.Split('/'); if (epmInfo.CriteriaValue != null) { ValidateConditionalMapping(epmInfo); } Debug.Assert(propertyPath.Length > 0, "Must have been validated during EntityPropertyMappingAttribute construction"); for (int sourcePropertyIndex = 0; sourcePropertyIndex < propertyPath.Length; sourcePropertyIndex++) { string propertyName = propertyPath[sourcePropertyIndex]; if (propertyName.Length == 0) { throw new ODataException(Strings.EpmSourceTree_InvalidSourcePath(epmInfo.DefiningType.Name, epmInfo.Attribute.SourcePath)); } bool isMultiValueProperty; currentType = GetPropertyType(currentType, propertyName, out isMultiValueProperty); foundSourceSegment = currentSourceSegment.SubProperties.SingleOrDefault(e => e.PropertyName == propertyName); if (foundSourceSegment != null) { currentSourceSegment = foundSourceSegment; } else { EpmSourcePathSegment newSourceSegment = new EpmSourcePathSegment(propertyName); currentSourceSegment.SubProperties.Add(newSourceSegment); currentSourceSegment = newSourceSegment; } pathToCurrentSegment.Add(currentSourceSegment); if (isMultiValueProperty) { Debug.Assert( currentSourceSegment.EpmInfo == null || currentSourceSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty, "MultiValue property must have EpmInfo marked as MultiValue or none at all."); Debug.Assert( currentSourceSegment.EpmInfo != null || foundSourceSegment == null, "The only way to get a propety without info attached yet on a MultiValue property is when we just created it."); if (multiValuePropertySegment != null) { // Nested MultiValue - not allowed to be mapped throw new ODataException(Strings.EpmSourceTree_NestedMultiValue( multiValuePropertySegment.EpmInfo.Attribute.SourcePath, multiValuePropertySegment.EpmInfo.DefiningType.Name, epmInfo.Attribute.SourcePath)); } multiValuePropertySegment = currentSourceSegment; // MultiValue properties can only be mapped to a top-level element, so we can blindly use the first part // of the target path as the target path for the MultiValue property. Debug.Assert(!string.IsNullOrEmpty(epmInfo.Attribute.TargetPath), "Target path should have been checked by the EpmAttribute constructor."); string multiValuePropertyTargetPath = epmInfo.Attribute.TargetPath.Split('/')[0]; if (currentSourceSegment.EpmInfo == null || !currentSourceSegment.EpmInfo.DefiningTypesAreEqual(epmInfo)) { if (currentSourceSegment.EpmInfo != null) { Debug.Assert(!currentSourceSegment.EpmInfo.DefiningTypesAreEqual(epmInfo), "Just verifying that the ifs are correct."); Debug.Assert(foundSourceSegment != null, "Can't have existing node with EpmInfo on it here and not found it before."); // If the MultiValue property we're trying to add is from a different type than the one we already have // just overwrite the epm info. This means that the derived type defines a different mapping for the property than the base type. // We also need to walk all the children of the base type mapping here and remove them from the target tree // since we're overriding the entire MultiValue property mapping (not just one item property) // Note that for MultiValue properties, removing the MultiValue property node itself will remove all the MultiValue item properties as well // as they have to be children of the MultiValue property node in the target tree. this.epmTargetTree.Remove(foundSourceSegment.EpmInfo); // We also need to remove all children of the MultiValue property node from the source tree // as the derived type is overriding it completely. If the derived type doesn't override all of the properties // we should fail as if it did't map all of them. currentSourceSegment.SubProperties.Clear(); } // This is the first time we've seen this MultiValue property mapped // (on this type, we might have seen it on the base type, but that has been removed) // The source path is the path we have so far for the property string multiValuePropertySourcePath = string.Join("/", propertyPath, 0, sourcePropertyIndex + 1); if (!epmInfo.IsSyndicationMapping) { // Custom EPM for MultiValue is not supported yet // Note: This has already been implemented, but then removed from the code. To see what it takes to implement this // please see the change which adds this comment into the sources. throw new ODataException(Strings.EpmSourceTree_MultiValueNotAllowedInCustomMapping( multiValuePropertySourcePath, epmInfo.DefiningType.Name)); } // Create a new EPM attribute to represent the MultiValue property mapping // note that this attribute is basically implicitly declared whenever the user declares EPM attribute // for a property from some MultiValue property. (the declaration happens right here) EntityPropertyMappingAttribute multiValueEpmAttribute = new EntityPropertyMappingAttribute( multiValuePropertySourcePath, multiValuePropertyTargetPath, epmInfo.Attribute.TargetNamespacePrefix, epmInfo.Attribute.TargetNamespaceUri, epmInfo.Attribute.KeepInContent); // Create a special EpmInfo from the above special attribute which represents just the MultiValue property itself EntityPropertyMappingInfo multiValueEpmInfo = new EntityPropertyMappingInfo( multiValueEpmAttribute, epmInfo.DefiningType, epmInfo.ActualPropertyType); multiValueEpmInfo.MultiValueStatus = EntityPropertyMappingMultiValueStatus.MultiValueProperty; multiValueEpmInfo.MultiValueItemType = currentType; // We need to mark the info as syndication/custom mapping explicitely since the attribute we create (From which it's infered) is always custom mapping Debug.Assert(epmInfo.IsSyndicationMapping, "Only syndication mapping is allowed for MultiValue properties."); multiValueEpmInfo.SetMultiValuePropertySyndicationMapping(); multiValueEpmInfo.SetPropertyValuePath(pathToCurrentSegment.ToArray()); multiValueEpmInfo.Criteria = epmInfo.Criteria; multiValueEpmInfo.CriteriaValue = epmInfo.CriteriaValue; // Now associate the current source tree segment with the new info object (or override the one from base) currentSourceSegment.EpmInfo = multiValueEpmInfo; // And add the new info to the target tree this.epmTargetTree.Add(multiValueEpmInfo); // And continue with the walk of the source path. // This means that the EPM attribute specified as the input to this method is still to be added // It might be added to the source tree if the path is longer (property on an item in the MultiValue property of complex types) // or it might not be added to the source tree if this segment is the last (MultiValue property of primitive types). // In any case it will be added to the target tree (so even if the MultiValue property itself is mapped to the top-level element only // the items in the MultiValue property can be mapped to child element/attribute of that top-level element). } else { Debug.Assert(currentSourceSegment.EpmInfo.DefiningTypesAreEqual(epmInfo), "The condition in the surrounding if is broken."); // We have already found a MultiValue property mapped from this source node. // If it's on the same defining type we need to make sure that it's the same MultiValue property being mapped // First verify that the mapping for the other property has the same top-level element for the MultiValue property // since we only allow properties from one MultiValue property to be mapped to the same top-level element if (multiValuePropertyTargetPath != currentSourceSegment.EpmInfo.Attribute.TargetPath || epmInfo.Attribute.TargetNamespacePrefix != currentSourceSegment.EpmInfo.Attribute.TargetNamespacePrefix || epmInfo.Attribute.TargetNamespaceUri != currentSourceSegment.EpmInfo.Attribute.TargetNamespaceUri || epmInfo.Criteria != currentSourceSegment.EpmInfo.Criteria || String.Compare(epmInfo.Attribute.CriteriaValue, currentSourceSegment.EpmInfo.CriteriaValue, StringComparison.OrdinalIgnoreCase) != 0) { throw new ODataException(Strings.EpmSourceTree_PropertiesFromSameMultiValueMappedToDifferentTopLevelElements(currentSourceSegment.EpmInfo.Attribute.SourcePath, currentSourceSegment.EpmInfo.DefiningType.Name)); } // Second verify that the mappings for both properties have the same KeepInContent value if (epmInfo.Attribute.KeepInContent != currentSourceSegment.EpmInfo.Attribute.KeepInContent) { throw new ODataException(Strings.EpmSourceTree_PropertiesFromSameMultiValueMappedWithDifferentKeepInContent(currentSourceSegment.EpmInfo.Attribute.SourcePath, currentSourceSegment.EpmInfo.DefiningType.Name)); } } } } // The last segment is the one being mapped from by the user specified attribute. // It must be a primitive type - we don't allow mappings of anything else than primitive properties directly. // Note that we can only verify this for non-open properties, for open properties we must assume it's a primitive type // and we will make this check later during serialization again when we actually have the value of the property. if (currentType != null) { if (currentType.ResourceTypeKind != ResourceTypeKind.Primitive) { throw new ODataException(Strings.EpmSourceTree_EndsWithNonPrimitiveType(currentSourceSegment.PropertyName)); } SyndicationItemProperty targetSyndicationItem = epmInfo.Attribute.TargetSyndicationItem; if (targetSyndicationItem == SyndicationItemProperty.LinkRel || targetSyndicationItem == SyndicationItemProperty.CategoryScheme) { if (PrimitiveStringResourceType != currentType) { throw new InvalidOperationException(Strings.EpmSourceTree_NonStringPropertyMappedToConditionAttribute( currentSourceSegment.PropertyName, epmInfo.DefiningType.FullName, targetSyndicationItem.ToString())); } } } if (multiValuePropertySegment == currentSourceSegment) { // If the MultiValue property is the last segment it means that the MultiValue property itself is being mapped (and it must be a MultiValue of primitive types). // If we found the MultiValue property already in the tree, here we actually want the item of the MultiValue property (as the MultiValue one was processed above already) // If we have the item value already in the tree use it as the foundProperty so that we correctly check the duplicate mappings below if (foundSourceSegment != null) { Debug.Assert(foundSourceSegment == currentSourceSegment, "If we found an existing segment it must be the current one."); foundSourceSegment = currentSourceSegment.SubProperties.SingleOrDefault(e => e.IsMultiValueItemValue); } if (foundSourceSegment == null) { // This is a bit of a special case. In the source tree we will create a special node to represent the item value (we need that to be able to tell // if it was not mapped twice). // In the target tree, we will also create a special node which will hold the information specific // to serialization of the item value (for example the exact syndication mapping target and so on). // The creation of the special node is done in the target tree Add method. EpmSourcePathSegment newSegment = EpmSourcePathSegment.CreateMultiValueItemValueSegment(); currentSourceSegment.SubProperties.Add(newSegment); currentSourceSegment = newSegment; } else { currentSourceSegment = foundSourceSegment; } } // Note that once we're here the EpmInfo we have is never the MultiValue property itself, it's always either a non-MultiValue property // or MultiValue item property. Debug.Assert(foundSourceSegment == null || foundSourceSegment.EpmInfo != null, "Can't have a leaf node in the tree without EpmInfo."); // Two EpmAttributes with same PropertyName in the same ResourceType, this could be a result of inheritance if (foundSourceSegment != null) { Debug.Assert(Object.ReferenceEquals(foundSourceSegment, currentSourceSegment), "currentSourceSegment variable should have been updated already to foundSourceSegment"); Debug.Assert( foundSourceSegment.EpmInfo.MultiValueStatus != EntityPropertyMappingMultiValueStatus.MultiValueProperty, "We should never get here with a MultiValue property itself, we should have a node represent its item or property on the item instead."); // Check for duplicates on the same entity type Debug.Assert(foundSourceSegment.SubProperties.Count == 0, "If non-leaf, it means we allowed complex type to be a leaf node"); if (foundSourceSegment.EpmInfo.DefiningTypesAreEqual(epmInfo)) { throw new ODataException(Strings.EpmSourceTree_DuplicateEpmAttrsWithSameSourceName(epmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name)); } // In case of inheritance, we need to remove the node from target tree which was mapped to base type property this.epmTargetTree.Remove(foundSourceSegment.EpmInfo); } epmInfo.SetPropertyValuePath(pathToCurrentSegment.ToArray()); currentSourceSegment.EpmInfo = epmInfo; if (multiValuePropertySegment != null) { Debug.Assert(multiValuePropertySegment.EpmInfo != null, "All MultiValue property segments must have EpmInfo assigned."); // We are mapping a MultiValue property - so mark the info as a MultiValue item property (since the MultiValue property itself was added above) epmInfo.MultiValueStatus = EntityPropertyMappingMultiValueStatus.MultiValueItemProperty; // Set the item type on each of the item properties, so that the segmented path know from which type to start epmInfo.MultiValueItemType = multiValuePropertySegment.EpmInfo.MultiValueItemType; // And trim its property value path to start from the MultiValue item. This path is basically a list of properties to traverse // when access the value of the property on the specified resource. For non-MultiValue and MultiValue properties themselves // this path starts with the entity instance. For MultiValue item properties this path starts with the MultiValue item instance. // Note that if it's a MultiValue of primitive types, the path is going to be empty meaning that the value is the item instance itself. epmInfo.TrimMultiValueItemPropertyPath(multiValuePropertySegment.EpmInfo); #if DEBUG // Check that if the MultiValue item is of primitive type, we can only ever add a single child source segment which points directly to the MultiValue property itself // If we would allow this here, we would fail later, but with a much weirder error message Debug.Assert( multiValuePropertySegment.EpmInfo.MultiValueItemType.ResourceTypeKind != ResourceTypeKind.Primitive || epmInfo.PropertyValuePath.Length == 0, "We shoud have failed to map a subproperty of a primitive MultiValue item."); #endif } this.epmTargetTree.Add(epmInfo); }
/// <summary> /// Adds a path to the source and target tree which is obtained by looking at the EntityPropertyMappingAttribute in the <paramref name="epmInfo"/> /// </summary> /// <param name="epmInfo">EnitityPropertyMappingInfo holding the source path</param> internal void Add(EntityPropertyMappingInfo epmInfo) { DebugUtils.CheckNoExternalCallers(); List<EpmSourcePathSegment> pathToCurrentSegment = new List<EpmSourcePathSegment>(); EpmSourcePathSegment currentSourceSegment = this.Root; EpmSourcePathSegment foundSourceSegment = null; IEdmType currentType = epmInfo.ActualPropertyType; Debug.Assert(!string.IsNullOrEmpty(epmInfo.Attribute.SourcePath), "Invalid source path"); string[] propertyPath = epmInfo.Attribute.SourcePath.Split('/'); int propertyPathLength = propertyPath.Length; Debug.Assert(propertyPathLength > 0, "Must have been validated during EntityPropertyMappingAttribute construction"); for (int sourcePropertyIndex = 0; sourcePropertyIndex < propertyPathLength; sourcePropertyIndex++) { string propertyName = propertyPath[sourcePropertyIndex]; if (propertyName.Length == 0) { throw new ODataException(o.Strings.EpmSourceTree_InvalidSourcePath(epmInfo.DefiningType.ODataFullName(), epmInfo.Attribute.SourcePath)); } IEdmTypeReference nextPropertyTypeReference = GetPropertyType(currentType, propertyName); IEdmType nextPropertyType = nextPropertyTypeReference == null ? null : nextPropertyTypeReference.Definition; // If we don't find a property type this is an open property; check whether this is the last segment in the path // since otherwise this would not be an open primitive property and only open primitive properties are allowed. if (nextPropertyType == null && sourcePropertyIndex < propertyPathLength - 1) { throw new ODataException(o.Strings.EpmSourceTree_OpenComplexPropertyCannotBeMapped(propertyName, currentType.ODataFullName())); } currentType = nextPropertyType; foundSourceSegment = currentSourceSegment.SubProperties.SingleOrDefault(e => e.PropertyName == propertyName); if (foundSourceSegment != null) { currentSourceSegment = foundSourceSegment; } else { EpmSourcePathSegment newSourceSegment = new EpmSourcePathSegment(propertyName); currentSourceSegment.SubProperties.Add(newSourceSegment); currentSourceSegment = newSourceSegment; } pathToCurrentSegment.Add(currentSourceSegment); } // The last segment is the one being mapped from by the user specified attribute. // It must be a primitive type - we don't allow mappings of anything else than primitive properties directly. // Note that we can only verify this for non-open properties, for open properties we must assume it's a primitive type // and we will make this check later during serialization again when we actually have the value of the property. if (currentType != null) { if (!currentType.IsODataPrimitiveTypeKind()) { throw new ODataException(o.Strings.EpmSourceTree_EndsWithNonPrimitiveType(currentSourceSegment.PropertyName)); } } Debug.Assert(foundSourceSegment == null || foundSourceSegment.EpmInfo != null, "Can't have a leaf node in the tree without EpmInfo."); // Two EpmAttributes with same PropertyName in the same type, this could be a result of inheritance if (foundSourceSegment != null) { Debug.Assert(object.ReferenceEquals(foundSourceSegment, currentSourceSegment), "currentSourceSegment variable should have been updated already to foundSourceSegment"); // Check for duplicates on the same entity type Debug.Assert(foundSourceSegment.SubProperties.Count == 0, "If non-leaf, it means we allowed complex type to be a leaf node"); if (foundSourceSegment.EpmInfo.DefiningTypesAreEqual(epmInfo)) { throw new ODataException(o.Strings.EpmSourceTree_DuplicateEpmAttributesWithSameSourceName(epmInfo.DefiningType.ODataFullName(), epmInfo.Attribute.SourcePath)); } // In case of inheritance, we need to remove the node from target tree which was mapped to base type property this.epmTargetTree.Remove(foundSourceSegment.EpmInfo); } epmInfo.SetPropertyValuePath(pathToCurrentSegment.ToArray()); currentSourceSegment.EpmInfo = epmInfo; this.epmTargetTree.Add(epmInfo); }
/// <summary> /// Validates conditional mapping. /// </summary> /// <param name="epmInfo">Epm mapping info</param> private static void ValidateConditionalMapping(EntityPropertyMappingInfo epmInfo) { Debug.Assert(epmInfo.CriteriaValue != null, "epmInfo.CriteriaValue != null"); String criteriaValue = epmInfo.CriteriaValue; if (epmInfo.Criteria == EpmSyndicationCriteria.LinkRel && !EntityPropertyMappingInfo.IsValidLinkRelCriteriaValue(criteriaValue)) { throw new ODataException(Strings.EpmSourceTree_ConditionalMappingInvalidLinkRelCriteriaValue( criteriaValue, epmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name)); } if (epmInfo.Criteria == EpmSyndicationCriteria.CategoryScheme && !EntityPropertyMappingInfo.IsValidCategorySchemeCriteriaValue(criteriaValue)) { throw new ODataException(Strings.EpmSourceTree_ConditionalMappingInvalidCategorySchemeCriteriaValue( criteriaValue, epmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name)); } if (epmInfo.IsAtomLinkMapping) { if (epmInfo.Attribute.TargetSyndicationItem == SyndicationItemProperty.LinkRel) { throw new ODataException(Strings.EpmTargetTree_ConditionalMappingToCriteriaAttribute( epmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name, epmInfo.Attribute.TargetPath)); } } else if (epmInfo.IsAtomCategoryMapping) { if (epmInfo.Attribute.TargetSyndicationItem == SyndicationItemProperty.CategoryScheme) { throw new ODataException(Strings.EpmTargetTree_ConditionalMappingToCriteriaAttribute( epmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name, epmInfo.Attribute.TargetPath)); } } else { throw new ODataException(Strings.EpmTargetTree_ConditionalMappingToNonConditionalSyndicationItem( epmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name, epmInfo.Attribute.TargetPath)); } }
private object ReadPropertyValue(EntityPropertyMappingInfo epmInfo, IEnumerable <ODataProperty> cachedProperties, int sourceSegmentIndex, IEdmStructuredTypeReference structuredTypeReference, EpmValueCache epmValueCache) { EpmSourcePathSegment segment = epmInfo.PropertyValuePath[sourceSegmentIndex]; string propertyName = segment.PropertyName; bool flag = epmInfo.PropertyValuePath.Length == (sourceSegmentIndex + 1); IEdmStructuredType owningStructuredType = structuredTypeReference.StructuredDefinition(); IEdmProperty expectedProperty = WriterValidationUtils.ValidatePropertyDefined(propertyName, owningStructuredType); if (expectedProperty != null) { if (flag) { if (!expectedProperty.Type.IsODataPrimitiveTypeKind() && !expectedProperty.Type.IsNonEntityODataCollectionTypeKind()) { throw new ODataException(Microsoft.Data.OData.Strings.EpmSourceTree_EndsWithNonPrimitiveType(propertyName)); } } else if (expectedProperty.Type.TypeKind() != EdmTypeKind.Complex) { throw new ODataException(Microsoft.Data.OData.Strings.EpmSourceTree_TraversalOfNonComplexType(propertyName)); } } ODataProperty property2 = (cachedProperties == null) ? null : cachedProperties.FirstOrDefault <ODataProperty>(p => (p.Name == propertyName)); if (property2 == null) { throw new ODataException(Microsoft.Data.OData.Strings.EpmSourceTree_MissingPropertyOnInstance(propertyName, structuredTypeReference.ODataFullName())); } object obj2 = property2.Value; ODataComplexValue complexValue = obj2 as ODataComplexValue; if (flag) { if (obj2 == null) { WriterValidationUtils.ValidateNullPropertyValue(expectedProperty, this.WriterBehavior, this.atomOutputContext.Model); return(obj2); } if (complexValue != null) { throw new ODataException(Microsoft.Data.OData.Strings.EpmSourceTree_EndsWithNonPrimitiveType(propertyName)); } ODataCollectionValue value3 = obj2 as ODataCollectionValue; if (value3 != null) { string str = value3.TypeName; WriterValidationUtils.ResolveTypeNameForWriting(this.atomOutputContext.Model, (expectedProperty == null) ? null : expectedProperty.Type, ref str, EdmTypeKind.Collection, expectedProperty == null); return(obj2); } if (obj2 is ODataStreamReferenceValue) { throw new ODataException(Microsoft.Data.OData.Strings.ODataWriter_StreamPropertiesMustBePropertiesOfODataEntry(propertyName)); } if (obj2 is ISpatial) { throw new ODataException(Microsoft.Data.OData.Strings.EpmSourceTree_OpenPropertySpatialTypeCannotBeMapped(propertyName, epmInfo.DefiningType.FullName())); } if (expectedProperty != null) { ValidationUtils.ValidateIsExpectedPrimitiveType(obj2, expectedProperty.Type); } return(obj2); } if (complexValue == null) { if (obj2 != null) { throw new ODataException(Microsoft.Data.OData.Strings.EpmSourceTree_TraversalOfNonComplexType(propertyName)); } return(null); } string typeName = complexValue.TypeName; IEdmComplexTypeReference complexType = WriterValidationUtils.ResolveTypeNameForWriting(this.atomOutputContext.Model, (expectedProperty == null) ? null : expectedProperty.Type, ref typeName, EdmTypeKind.Complex, expectedProperty == null).AsComplexOrNull(); return(this.ReadComplexPropertyValue(epmInfo, complexValue, epmValueCache, sourceSegmentIndex + 1, complexType)); }
/// <summary>Adds a property to the null valued collection</summary> /// <param name="epmInfo">EpmInfo containing the property information such as path</param> internal void Add(EntityPropertyMappingInfo epmInfo) { Debug.Assert(epmInfo != null, "epmInfo != null"); EpmNullValuedPropertyNode current = this.root; ResourceType currentType = epmInfo.DefiningType; object currentValue = this.element; // We are here because the epm path points to a null value. If the path is multiple level deep, we need to // know the first level the null value begins and we don't need to serialize deeper than that. // To serialize the complex properties correctly in the case they are not already in content, we also need // to find the type for each segment from root to the first segment that has the null property value. foreach (var segment in epmInfo.Attribute.SourcePath.Split('/')) { EpmNullValuedPropertyNode child = current.Children.FirstOrDefault(c => c.Name == segment); if (child != null) { // The current segment is already added to the tree, reuse it. current = child; currentValue = child.Element; currentType = child.ResourceType; } else { EpmNullValuedPropertyNode newNode = new EpmNullValuedPropertyNode { Name = segment }; Debug.Assert(currentType != null, "currentType != null"); ResourceProperty property = currentType.TryResolvePropertyName(segment); Debug.Assert(currentValue != null, "currentValue != null"); ProjectedWrapper projectedValue = currentValue as ProjectedWrapper; if (projectedValue == null) { if (property != null) { currentValue = this.provider.GetPropertyValue(currentValue, property, currentType); currentValue = currentValue == DBNull.Value ? null : currentValue; currentType = property.ResourceType; } else { // Handle open property... currentValue = this.provider.GetOpenPropertyValue(currentValue, segment); currentValue = currentValue == DBNull.Value ? null : currentValue; if (currentValue != null) { // Get the type from the instance. currentType = this.provider.GetResourceType(currentValue); } else { // We have a null open property at hand, we don't know its type. // Default the type to string so that we will omit the type name // and just write out null. i.e. <d:prop m:null='true'/> currentType = ResourceType.PrimitiveStringResourceType; } } } else { currentValue = projectedValue.GetProjectedPropertyValue(segment); currentValue = currentValue == DBNull.Value ? null : currentValue; if (property != null) { currentType = property.ResourceType; } else { // Handle open property... if (currentValue == null) { // We have a null open property at hand, we don't know its type. // Default the type to string so that we will omit the type name // and just write out null. i.e. <d:prop m:null='true'/> currentType = ResourceType.PrimitiveStringResourceType; } else { projectedValue = currentValue as ProjectedWrapper; if (projectedValue != null) { // Get the type from the project wrapper. currentType = this.provider.TryResolveResourceType(projectedValue.ResourceTypeName); } else { // Get the type from the instance. currentType = this.provider.GetResourceType(currentValue); } } } } Debug.Assert(currentType != null, "currentType != null"); Debug.Assert(currentValue != DBNull.Value, "currentValue != DBNull.Value -- we have converted DBNull to null"); newNode.ResourceType = currentType; newNode.Element = currentValue; current.Children.Add(newNode); current = newNode; } if (current.Element == null) { // If the current element is null, we don't need to go further since that is the obvious reason // that the children properties are null. break; } } }
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; for (int i = 0; i < targetSegments.Length; i++) { string targetSegment = targetSegments[i]; if (targetSegment.Length == 0) { throw new ODataException(Strings.EpmTargetTree_InvalidTargetPath_EmptySegment(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)); if (foundSegment != null) { currentSegment = foundSegment; } else { currentSegment = new EpmTargetPathSegment(targetSegment, namespaceUri, namespacePrefix, currentSegment); if (targetSegment[0] == '@') { activeSubSegments.Insert(0, currentSegment); } else { activeSubSegments.Add(currentSegment); } } activeSubSegments = currentSegment.SubSegments; } if (currentSegment.EpmInfo != null) { // Two EpmAttributes with same TargetName in the inheritance hierarchy throw new ODataException(Strings.EpmTargetTree_DuplicateEpmAttributesWithSameTargetName(currentSegment.EpmInfo.DefiningType.ODataFullName(), EpmTargetTree.GetPropertyNameFromEpmInfo(currentSegment.EpmInfo), currentSegment.EpmInfo.Attribute.SourcePath, epmInfo.Attribute.SourcePath)); } // Increment the number of properties for which KeepInContent is false if (!epmInfo.Attribute.KeepInContent) { this.countOfNonContentV2Mappings++; } currentSegment.EpmInfo = epmInfo; // Mixed content is dis-allowed. List<EntityPropertyMappingAttribute> conflictingAttributes = new List<EntityPropertyMappingAttribute>(2); if (EpmTargetTree.HasMixedContent(this.NonSyndicationRoot, conflictingAttributes)) { Debug.Assert(conflictingAttributes.Count == 2, "Expected to find exactly two conflicting attributes."); throw new ODataException(Strings.EpmTargetTree_InvalidTargetPath_MixedContent(conflictingAttributes[0].TargetPath, conflictingAttributes[1].TargetPath)); } }
/// <summary>Compares the defining type of this info and other EpmInfo object.</summary> /// <param name="other">The other EpmInfo object to compare to.</param> /// <returns>true if the defining types are the same</returns> internal bool DefiningTypesAreEqual(EntityPropertyMappingInfo other) { return(this.DefiningType == other.DefiningType); }
/// <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"); 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)); 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) { this.countOfNonContentV2Mappings--; } EpmTargetPathSegment parentSegment = null; do { parentSegment = currentSegment.ParentSegment; parentSegment.SubSegments.Remove(currentSegment); currentSegment = parentSegment; } while (currentSegment.ParentSegment != null && !currentSegment.HasContent && currentSegment.SubSegments.Count == 0); } }
/// <summary> /// Given an <see cref="EntityPropertyMappingInfo"/> gives the correct target path for it /// </summary> /// <param name="epmInfo">Given <see cref="EntityPropertyMappingInfo"/></param> /// <returns>string with the correct value for the target path</returns> private static string GetPropertyNameFromEpmInfo(EntityPropertyMappingInfo epmInfo) { if (epmInfo.Attribute.TargetSyndicationItem == SyndicationItemProperty.CustomProperty) { return epmInfo.Attribute.TargetPath; } // for EF provider we want to return a name that corresponds to attribute in the edmx file while for CLR provider // and the client we want to return a name that corresponds to the enum value used in EntityPropertyMapping attribute. return #if ASTORIA_SERVER epmInfo.IsEFProvider ? EpmTranslate.MapSyndicationPropertyToEpmTargetPath(epmInfo.Attribute.TargetSyndicationItem) : #endif epmInfo.Attribute.TargetSyndicationItem.ToString(); }
internal void Add(EntityPropertyMappingInfo epmInfo) {
/// <summary> /// Writes the syndication part of EPM for an entry into ATOM metadata OM. /// </summary> /// <param name="epmValueCache">The entry properties value cache to use to access the properties.</param> /// <param name="entityType">The type of the entry.</param> /// <returns>The ATOM metadata OM with the EPM values populated.</returns> private AtomEntryMetadata WriteEntryEpm( EntryPropertiesValueCache epmValueCache, IEdmEntityTypeReference entityType) { // If there are no syndication mappings, just return null. EpmTargetPathSegment syndicationRootSegment = this.epmTargetTree.SyndicationRoot; Debug.Assert(syndicationRootSegment != null, "EPM Target tree must always have syndication root."); if (syndicationRootSegment.SubSegments.Count == 0) { return(null); } foreach (EpmTargetPathSegment targetSegment in syndicationRootSegment.SubSegments) { 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"); object propertyValue = this.ReadEntryPropertyValue( epmInfo, epmValueCache, entityType); string textPropertyValue = EpmWriterUtils.GetPropertyValueAsText(propertyValue); switch (epmInfo.Attribute.TargetSyndicationItem) { case SyndicationItemProperty.Updated: if (this.WriterBehavior.FormatBehaviorKind == ODataBehaviorKind.WcfDataServicesClient) { this.entryMetadata.UpdatedString = EpmSyndicationWriter.CreateDateTimeStringValue(propertyValue, this.WriterBehavior); } else { this.entryMetadata.Updated = EpmSyndicationWriter.CreateDateTimeValue(propertyValue, SyndicationItemProperty.Updated, this.WriterBehavior); } break; case SyndicationItemProperty.Published: if (this.WriterBehavior.FormatBehaviorKind == ODataBehaviorKind.WcfDataServicesClient) { this.entryMetadata.PublishedString = EpmSyndicationWriter.CreateDateTimeStringValue(propertyValue, this.WriterBehavior); } else { this.entryMetadata.Published = EpmSyndicationWriter.CreateDateTimeValue(propertyValue, SyndicationItemProperty.Published, this.WriterBehavior); } break; case SyndicationItemProperty.Rights: this.entryMetadata.Rights = EpmSyndicationWriter.CreateAtomTextConstruct(textPropertyValue, epmInfo.Attribute.TargetTextContentKind); break; case SyndicationItemProperty.Summary: this.entryMetadata.Summary = EpmSyndicationWriter.CreateAtomTextConstruct(textPropertyValue, epmInfo.Attribute.TargetTextContentKind); break; case SyndicationItemProperty.Title: this.entryMetadata.Title = EpmSyndicationWriter.CreateAtomTextConstruct(textPropertyValue, epmInfo.Attribute.TargetTextContentKind); break; default: throw new ODataException(o.Strings.General_InternalError(InternalErrorCodes.EpmSyndicationWriter_WriteEntryEpm_ContentTarget)); } } else { this.WriteParentSegment(targetSegment, epmValueCache, entityType); } } return(this.entryMetadata); }
internal void Add(EntityPropertyMappingInfo epmInfo, IEnumerable <ResourceProperty> declaredProperties) { Dictionary <ResourceType, IEnumerable <ResourceProperty> > declaredPropertiesLookup = new Dictionary <ResourceType, IEnumerable <ResourceProperty> >(EqualityComparer <ResourceType> .Default); declaredPropertiesLookup.Add(epmInfo.ActualPropertyType, declaredProperties); #endif EpmSourcePathSegment currentSourceSegment = this.Root; EpmSourcePathSegment foundSourceSegment = null; ClientTypeOrResourceType_Alias currentType = epmInfo.ActualPropertyType; Debug.Assert(epmInfo.PropertyValuePath != null && epmInfo.PropertyValuePath.Length > 0, "Must have been validated during EntityPropertyMappingAttribute construction"); for (int sourcePropertyIndex = 0; sourcePropertyIndex < epmInfo.PropertyValuePath.Length; sourcePropertyIndex++) { string propertyName = epmInfo.PropertyValuePath[sourcePropertyIndex]; if (propertyName.Length == 0) { throw new InvalidOperationException(c.Strings.EpmSourceTree_InvalidSourcePath(epmInfo.DefiningType.Name, epmInfo.Attribute.SourcePath)); } #if ASTORIA_CLIENT currentType = GetPropertyType(currentType, propertyName); #else currentType = GetPropertyType(currentType, propertyName, declaredPropertiesLookup); #endif foundSourceSegment = currentSourceSegment.SubProperties.SingleOrDefault(e => e.PropertyName == propertyName); if (foundSourceSegment != null) { currentSourceSegment = foundSourceSegment; } else { EpmSourcePathSegment newSourceSegment = new EpmSourcePathSegment(propertyName); currentSourceSegment.SubProperties.Add(newSourceSegment); currentSourceSegment = newSourceSegment; } } // The last segment is the one being mapped from by the user specified attribute. // It must be a primitive type - we don't allow mappings of anything else than primitive properties directly. // Note that we can only verify this for non-open properties, for open properties we must assume it's a primitive type // and we will make this check later during serialization again when we actually have the value of the property. if (currentType != null) { #if ASTORIA_CLIENT if (!PrimitiveType.IsKnownNullableType(currentType.ElementType)) #else if (currentType.ResourceTypeKind != ResourceTypeKind.Primitive) #endif { throw new InvalidOperationException(c.Strings.EpmSourceTree_EndsWithNonPrimitiveType(currentSourceSegment.PropertyName)); } } // Note that once we're here the EpmInfo we have is never the collection property itself, it's always either a non-collection property // or collection item property. Debug.Assert(foundSourceSegment == null || foundSourceSegment.EpmInfo != null, "Can't have a leaf node in the tree without EpmInfo."); // Two EpmAttributes with same PropertyName in the same ResourceType, this could be a result of inheritance if (foundSourceSegment != null) { Debug.Assert(Object.ReferenceEquals(foundSourceSegment, currentSourceSegment), "currentSourceSegment variable should have been updated already to foundSourceSegment"); // Check for duplicates on the same entity type Debug.Assert(foundSourceSegment.SubProperties.Count == 0, "If non-leaf, it means we allowed complex type to be a leaf node"); if (foundSourceSegment.EpmInfo.DefiningTypesAreEqual(epmInfo)) { throw new InvalidOperationException(c.Strings.EpmSourceTree_DuplicateEpmAttrsWithSameSourceName(epmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name)); } // In case of inheritance, we need to remove the node from target tree which was mapped to base type property this.epmTargetTree.Remove(foundSourceSegment.EpmInfo); } currentSourceSegment.EpmInfo = epmInfo; this.epmTargetTree.Add(epmInfo); }
private void SetEpmValueForSegment(EntityPropertyMappingInfo epmInfo, int propertyValuePathIndex, IEdmStructuredTypeReference segmentStructuralTypeReference, List <ODataProperty> existingProperties, object propertyValue) { string propertyName = epmInfo.PropertyValuePath[propertyValuePathIndex].PropertyName; if (!epmInfo.Attribute.KeepInContent) { IEdmTypeReference type; ODataProperty property = existingProperties.FirstOrDefault <ODataProperty>(p => string.CompareOrdinal(p.Name, propertyName) == 0); ODataComplexValue value2 = null; if (property != null) { value2 = property.Value as ODataComplexValue; if (value2 == null) { return; } } IEdmProperty property2 = segmentStructuralTypeReference.FindProperty(propertyName); if ((property2 == null) && (propertyValuePathIndex != (epmInfo.PropertyValuePath.Length - 1))) { throw new ODataException(Microsoft.Data.OData.Strings.EpmReader_OpenComplexOrCollectionEpmProperty(epmInfo.Attribute.SourcePath)); } if ((property2 == null) || (this.MessageReaderSettings.DisablePrimitiveTypeConversion && property2.Type.IsODataPrimitiveTypeKind())) { type = EdmCoreModel.Instance.GetString(true); } else { type = property2.Type; } switch (type.TypeKind()) { case EdmTypeKind.Primitive: object obj2; if (type.IsStream()) { throw new ODataException(Microsoft.Data.OData.Strings.General_InternalError(InternalErrorCodes.EpmReader_SetEpmValueForSegment_StreamProperty)); } if (propertyValue == null) { ReaderValidationUtils.ValidateNullValue(this.atomInputContext.Model, type, this.atomInputContext.MessageReaderSettings, true, this.atomInputContext.Version); obj2 = null; } else { obj2 = AtomValueUtils.ConvertStringToPrimitive((string)propertyValue, type.AsPrimitive()); } this.AddEpmPropertyValue(existingProperties, propertyName, obj2, segmentStructuralTypeReference.IsODataEntityTypeKind()); return; case EdmTypeKind.Complex: { if (value2 == null) { value2 = new ODataComplexValue { TypeName = type.ODataFullName(), Properties = new ReadOnlyEnumerable <ODataProperty>() }; this.AddEpmPropertyValue(existingProperties, propertyName, value2, segmentStructuralTypeReference.IsODataEntityTypeKind()); } IEdmComplexTypeReference reference2 = type.AsComplex(); this.SetEpmValueForSegment(epmInfo, propertyValuePathIndex + 1, reference2, ReaderUtils.GetPropertiesList(value2.Properties), propertyValue); return; } case EdmTypeKind.Collection: { ODataCollectionValue value4 = new ODataCollectionValue { TypeName = type.ODataFullName(), Items = new ReadOnlyEnumerable((List <object>)propertyValue) }; this.AddEpmPropertyValue(existingProperties, propertyName, value4, segmentStructuralTypeReference.IsODataEntityTypeKind()); return; } } throw new ODataException(Microsoft.Data.OData.Strings.General_InternalError(InternalErrorCodes.EpmReader_SetEpmValueForSegment_TypeKind)); } }
private object ReadComplexPropertyValue(EntityPropertyMappingInfo epmInfo, ODataComplexValue complexValue, EpmValueCache epmValueCache, int sourceSegmentIndex, IEdmComplexTypeReference complexType) { return(this.ReadPropertyValue(epmInfo, EpmValueCache.GetComplexValueProperties(epmValueCache, complexValue, false), sourceSegmentIndex, complexType, epmValueCache)); }