/// <summary> /// Constructor which creates an empty root. /// </summary> /// <param name="epmTargetTree">Target xml tree</param> internal EpmSourceTree(EpmTargetTree epmTargetTree) { DebugUtils.CheckNoExternalCallers(); this.root = new EpmSourcePathSegment(); this.epmTargetTree = epmTargetTree; }
private bool ShouldWritePropertyInContent(IEdmStructuredType owningType, ProjectedPropertiesAnnotation projectedProperties, string propertyName, object propertyValue, EpmSourcePathSegment epmSourcePathSegment) { bool flag = !projectedProperties.ShouldSkipProperty(propertyName); if ((((base.MessageWriterSettings.WriterBehavior != null) && base.MessageWriterSettings.WriterBehavior.UseV1ProviderBehavior) && (owningType != null)) && owningType.IsODataComplexTypeKind()) { IEdmComplexType complexType = (IEdmComplexType) owningType; CachedPrimitiveKeepInContentAnnotation annotation = base.Model.EpmCachedKeepPrimitiveInContent(complexType); if ((annotation != null) && annotation.IsKeptInContent(propertyName)) { return flag; } } if ((propertyValue == null) && (epmSourcePathSegment != null)) { return true; } EntityPropertyMappingAttribute entityPropertyMapping = EpmWriterUtils.GetEntityPropertyMapping(epmSourcePathSegment); if (entityPropertyMapping == null) { return flag; } string str = propertyValue as string; if ((str != null) && (str.Length == 0)) { switch (entityPropertyMapping.TargetSyndicationItem) { case SyndicationItemProperty.AuthorEmail: case SyndicationItemProperty.AuthorUri: case SyndicationItemProperty.ContributorEmail: case SyndicationItemProperty.ContributorUri: return true; } } return (entityPropertyMapping.KeepInContent && flag); }
/// <summary> /// Determines if the property with the specified value should be written into content or not. /// </summary> /// <param name="owningType">The owning type of the property to be checked.</param> /// <param name="projectedProperties">The set of projected properties for the <paramref name="owningType"/></param> /// <param name="propertyName">The name of the property to be checked.</param> /// <param name="propertyValue">The property value to write.</param> /// <param name="epmSourcePathSegment">The EPM source path segment for the property being written.</param> /// <returns>true if the property should be written into content, or false otherwise</returns> private bool ShouldWritePropertyInContent( IEdmStructuredType owningType, ProjectedPropertiesAnnotation projectedProperties, string propertyName, object propertyValue, EpmSourcePathSegment epmSourcePathSegment) { // check whether the property is projected; if no EPM is specified for the property the projection decides bool propertyProjected = !projectedProperties.ShouldSkipProperty(propertyName); bool useV1ProviderBehavior = this.MessageWriterSettings.WriterBehavior == null ? false : this.MessageWriterSettings.WriterBehavior.UseV1ProviderBehavior; if (useV1ProviderBehavior && owningType != null && owningType.IsODataComplexTypeKind()) { IEdmComplexType owningComplexType = (IEdmComplexType)owningType; CachedPrimitiveKeepInContentAnnotation keepInContentAnnotation = this.Model.EpmCachedKeepPrimitiveInContent(owningComplexType); if (keepInContentAnnotation != null && keepInContentAnnotation.IsKeptInContent(propertyName)) { return(propertyProjected); } } // We sometimes write properties into content even if asked not to. // If the property value is null and the property (or one of its descendant properties) is mapped, // we always write into content, even if the property was not projected. if (propertyValue == null && epmSourcePathSegment != null) { return(true); } EntityPropertyMappingAttribute entityPropertyMapping = EpmWriterUtils.GetEntityPropertyMapping(epmSourcePathSegment); if (entityPropertyMapping == null) { return(propertyProjected); } string stringPropertyValue = propertyValue as string; if (stringPropertyValue != null && stringPropertyValue.Length == 0) { // If the property value is an empty string and we should be writing it into an ATOM element which does not allow empty string // we write it into content as well, also even if the property was not projected. switch (entityPropertyMapping.TargetSyndicationItem) { case SyndicationItemProperty.AuthorEmail: case SyndicationItemProperty.AuthorUri: case SyndicationItemProperty.ContributorEmail: case SyndicationItemProperty.ContributorUri: return(true); default: break; } } return(entityPropertyMapping.KeepInContent && propertyProjected); }
/// <summary> /// Gets the <see cref="EntityPropertyMappingAttribute"/> for the specified <paramref name="propertyName"/> /// from the <paramref name="epmParentSourcePathSegment"/>. /// </summary> /// <param name="epmParentSourcePathSegment">The EPM source path segment for the parent of the property being written.</param> /// <param name="propertyName">The name of the property to get the <see cref="EntityPropertyMappingAttribute"/> for.</param> /// <returns>The <see cref="EntityPropertyMappingAttribute"/> for the specified <paramref name="propertyName"/> or null if none exists.</returns> internal static EntityPropertyMappingAttribute GetEntityPropertyMapping(EpmSourcePathSegment epmParentSourcePathSegment, string propertyName) { DebugUtils.CheckNoExternalCallers(); ExceptionUtils.CheckArgumentStringNotNullOrEmpty(propertyName, "propertyName"); EpmSourcePathSegment epmSourcePathSegment = GetPropertySourcePathSegment(epmParentSourcePathSegment, propertyName); return(GetEntityPropertyMapping(epmSourcePathSegment)); }
/// <summary> /// Cache all the properties and collection item enumerations needed for EPM processing. /// </summary> /// <param name="propertyValueCache">The property value cache to cache the EPM related properties in.</param> /// <param name="sourceTree">The source tree describing all properties taking part in entity property mappings.</param> internal static void CacheEpmProperties(EntryPropertiesValueCache propertyValueCache, EpmSourceTree sourceTree) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(propertyValueCache != null, "propertyValueCache != null"); Debug.Assert(sourceTree != null, "sourceTree != null"); EpmSourcePathSegment rootSegment = sourceTree.Root; Debug.Assert(rootSegment.PropertyName == null, "Root segment should have 'null' property name."); CacheEpmSourcePathSegments(propertyValueCache, rootSegment.SubProperties, propertyValueCache.EntryProperties); }
/// <summary> /// Returns an <see cref="EpmSourcePathSegment"/> for a given property provided the parent <see cref="EpmSourcePathSegment"/>. /// </summary> /// <param name="epmParentSourcePathSegment">The parent <see cref="EpmSourcePathSegment"/> to get the property segment from.</param> /// <param name="propertyName">The name of the property to get the <see cref="EpmSourcePathSegment"/> for.</param> /// <returns>An <see cref="EpmSourcePathSegment"/> for a given property provided the parent <see cref="EpmSourcePathSegment"/>.</returns> internal static EpmSourcePathSegment GetPropertySourcePathSegment(EpmSourcePathSegment epmParentSourcePathSegment, string propertyName) { DebugUtils.CheckNoExternalCallers(); EpmSourcePathSegment epmSourcePathSegment = null; if (epmParentSourcePathSegment != null) { epmSourcePathSegment = epmParentSourcePathSegment.SubProperties.FirstOrDefault(subProperty => subProperty.PropertyName == propertyName); } return(epmSourcePathSegment); }
/// <summary> /// Writes a property with a complex value in ATOM format. /// </summary> /// <param name="complexValue">The complex value to write.</param> /// <param name="propertyName">The name of the property being written.</param> /// <param name="isTopLevel">true if writing a top-level property payload; otherwise false.</param> /// <param name="isWritingCollection">true if we are writing a top-level collection instead of an entry.</param> /// <param name="beforeValueAction">Action called before the complex value is written, if it's actually written.</param> /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param> /// <param name="propertyTypeReference">The type information for the property being written.</param> /// <param name="isOpenPropertyType">true if the type name belongs to an open property.</param> /// <param name="epmSourcePathSegment">The EPM source path segment which points to the property we're writing. (can be null)</param> /// <param name="complexValueProjectedProperties">Set of projected properties, or null if all properties should be written.</param> /// <returns>true if anything was written, false otherwise.</returns> private bool WriteComplexValueProperty( ODataComplexValue complexValue, string propertyName, bool isTopLevel, bool isWritingCollection, Action beforeValueAction, EpmValueCache epmValueCache, IEdmTypeReference propertyTypeReference, bool isOpenPropertyType, EpmSourcePathSegment epmSourcePathSegment, ProjectedPropertiesAnnotation complexValueProjectedProperties) { // Complex properties are written recursively. DuplicatePropertyNamesChecker complexValuePropertyNamesChecker = this.CreateDuplicatePropertyNamesChecker(); if (isTopLevel) { // Top-level property must always write the property element Debug.Assert(complexValueProjectedProperties == null, "complexValueProjectedProperties == null"); this.WritePropertyStart(beforeValueAction, propertyName, isWritingCollection, /*isTopLevel*/ true); this.AssertRecursionDepthIsZero(); this.WriteComplexValue( complexValue, propertyTypeReference, isOpenPropertyType, isWritingCollection, null /* beforeValueAction */, null /* afterValueAction */, complexValuePropertyNamesChecker, null /* collectionValidator */, epmValueCache, epmSourcePathSegment, null /* projectedProperties */); this.AssertRecursionDepthIsZero(); this.WritePropertyEnd(); return(true); } return(this.WriteComplexValue( complexValue, propertyTypeReference, isOpenPropertyType, isWritingCollection, () => this.WritePropertyStart(beforeValueAction, propertyName, isWritingCollection, /*isTopLevel*/ false), this.WritePropertyEnd, complexValuePropertyNamesChecker, null /* collectionValidator */, epmValueCache, epmSourcePathSegment, complexValueProjectedProperties)); }
internal static EntityPropertyMappingAttribute GetEntityPropertyMapping(EpmSourcePathSegment epmSourcePathSegment) { if (epmSourcePathSegment == null) { return(null); } EntityPropertyMappingInfo epmInfo = epmSourcePathSegment.EpmInfo; if (epmInfo == null) { return(null); } return(epmInfo.Attribute); }
internal static EpmSourcePathSegment GetPropertySourcePathSegment(EpmSourcePathSegment epmParentSourcePathSegment, string propertyName) { Func <EpmSourcePathSegment, bool> predicate = null; EpmSourcePathSegment segment = null; if (epmParentSourcePathSegment == null) { return(segment); } if (predicate == null) { predicate = subProperty => subProperty.PropertyName == propertyName; } return(epmParentSourcePathSegment.SubProperties.FirstOrDefault <EpmSourcePathSegment>(predicate)); }
/// <summary> /// Write the given collection of properties. /// </summary> /// <param name="owningType">The <see cref="IEdmStructuredType"/> of the entry (or null if not metadata is available).</param> /// <param name="cachedProperties">Collection of cached properties for the entry.</param> /// <param name="isWritingCollection">true if we are writing a top level collection instead of an entry.</param> /// <param name="beforePropertiesAction">Action which is called before the properties are written, if there are any property.</param> /// <param name="afterPropertiesAction">Action which is called after the properties are written, if there are any property.</param> /// <param name="duplicatePropertyNamesChecker">The checker instance for duplicate property names.</param> /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param> /// <param name="epmSourcePathSegment">The EPM source path segment which points to the property which sub-properites we're writing. (can be null)</param> /// <param name="projectedProperties">Set of projected properties, or null if all properties should be written.</param> /// <returns>true if anything was written, false otherwise.</returns> internal bool WriteProperties( IEdmStructuredType owningType, IEnumerable <ODataProperty> cachedProperties, bool isWritingCollection, Action beforePropertiesAction, Action afterPropertiesAction, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, EpmValueCache epmValueCache, EpmSourcePathSegment epmSourcePathSegment, ProjectedPropertiesAnnotation projectedProperties) { DebugUtils.CheckNoExternalCallers(); if (cachedProperties == null) { return(false); } bool propertyWritten = false; foreach (ODataProperty property in cachedProperties) { propertyWritten |= this.WriteProperty( property, owningType, /*isTopLevel*/ false, isWritingCollection, propertyWritten ? null : beforePropertiesAction, epmValueCache, epmSourcePathSegment, duplicatePropertyNamesChecker, projectedProperties); } if (afterPropertiesAction != null && propertyWritten) { afterPropertiesAction(); } return(propertyWritten); }
/// <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>Validates the specified segment and all its subsegments.</summary> /// <param name="pathSegment">The path segment to validate.</param> /// <param name="resourceType">The resource type of the property represented by this segment (null for open properties).</param> /// <param name="declaredPropertiesLookup">The dictionary to lookup and add declaredProperties associated with resourceTypes.</param> private static void Validate(EpmSourcePathSegment pathSegment, ClientTypeOrResourceType_Alias resourceType, Dictionary <ResourceType, IEnumerable <ResourceProperty> > declaredPropertiesLookup) #endif { Debug.Assert(pathSegment != null, "pathSegment != null"); foreach (EpmSourcePathSegment subSegment in pathSegment.SubProperties) { #if ASTORIA_CLIENT ClientTypeOrResourceType_Alias subSegmentResourceType = GetPropertyType(resourceType, subSegment.PropertyName); // sometimes the previous call returns null, WHY do we even bother // to continue on after we can't find a resourceType? Validate(subSegment, subSegmentResourceType); #else ClientTypeOrResourceType_Alias subSegmentResourceType = GetPropertyType(resourceType, subSegment.PropertyName, declaredPropertiesLookup); // sometimes the previous call returns null, WHY do we even bother // to continue on after we can't find a resourceType? Validate(subSegment, subSegmentResourceType, declaredPropertiesLookup); #endif } }
/// <summary> /// Writes a single property in ATOM format. /// </summary> /// <param name="writer">The <see cref="XmlWriter"/> to write to.</param> /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param> /// <param name="property">The property to write out.</param> /// <param name="owningType">The type owning the property (or null if no metadata is available).</param> /// <param name="version">The protocol version used for writing.</param> /// <param name="isTopLevel">True if writing a top-level property payload; otherwise false.</param> /// <param name="isWritingCollection">True if we are writing a collection instead of an entry.</param> /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param> /// <param name="epmParentSourcePathSegment">The EPM source path segment which points to the property which sub-property we're writing. (can be null)</param> internal static void WriteProperty( XmlWriter writer, DataServiceMetadataProviderWrapper metadata, ODataProperty property, ResourceType owningType, ODataVersion version, bool isTopLevel, bool isWritingCollection, EpmValueCache epmValueCache, EpmSourcePathSegment epmParentSourcePathSegment) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(writer != null, "writer != null"); ValidationUtils.ValidateProperty(property); ResourceProperty resourceProperty = ValidationUtils.ValidatePropertyDefined(property.Name, owningType); EpmSourcePathSegment epmSourcePathSegment = null; if (epmParentSourcePathSegment != null) { epmSourcePathSegment = epmParentSourcePathSegment.SubProperties.Where(subProperty => subProperty.PropertyName == property.Name).FirstOrDefault(); } object value = property.Value; // TODO: If we implement validation or type conversions the value needs to be converted here // since the next method call needs to know if the value is a string or not in some cases. // If EPM tells us to skip this property in content, then we're done here. if (!ShouldWritePropertyInContent(value, epmSourcePathSegment, version)) { return; } // <d:propertyname> writer.WriteStartElement( isWritingCollection ? string.Empty : AtomConstants.ODataNamespacePrefix, property.Name, AtomConstants.ODataNamespace); if (isTopLevel) { WriteDefaultNamespaceAttributes(writer, DefaultNamespaceFlags.OData | DefaultNamespaceFlags.ODataMetadata); } // Null property value. if (value == null) { // verify that MultiValue properties are not null if (resourceProperty != null && resourceProperty.Kind == ResourcePropertyKind.MultiValue) { throw new ODataException(Strings.ODataWriter_MultiValuePropertiesMustNotHaveNullValue(resourceProperty.Name)); } ODataAtomWriterUtils.WriteNullAttribute(writer); } else { ODataComplexValue complexValue = value as ODataComplexValue; ResourceType resourcePropertyType = resourceProperty == null ? null : resourceProperty.ResourceType; bool isOpenPropertyType = owningType != null && owningType.IsOpenType && resourceProperty == null; // Complex properties are written recursively. if (complexValue != null) { WriteComplexValue(writer, metadata, complexValue, resourcePropertyType, isOpenPropertyType, isWritingCollection, version, epmValueCache, epmSourcePathSegment); } else { ODataMultiValue multiValue = value as ODataMultiValue; if (multiValue != null) { ODataVersionChecker.CheckMultiValueProperties(version, property.Name); WriteMultiValue(writer, metadata, multiValue, resourcePropertyType, isOpenPropertyType, isWritingCollection, version, epmValueCache, epmSourcePathSegment); } else { WritePrimitiveValue(writer, value, resourcePropertyType); } } } // </d:propertyname> writer.WriteEndElement(); }
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> /// 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)); } } } }
internal void SetPropertyValuePath(EpmSourcePathSegment[] path) { this.propertyValuePath = path; }
private bool WriteProperty(ODataProperty property, IEdmStructuredType owningType, bool isTopLevel, bool isWritingCollection, Action beforePropertyAction, EpmValueCache epmValueCache, EpmSourcePathSegment epmParentSourcePathSegment, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, ProjectedPropertiesAnnotation projectedProperties) { Action beforeValueAction = null; Action afterValueAction = null; WriterValidationUtils.ValidatePropertyNotNull(property); object propertyValue = property.Value; string propertyName = property.Name; EpmSourcePathSegment propertySourcePathSegment = EpmWriterUtils.GetPropertySourcePathSegment(epmParentSourcePathSegment, propertyName); ODataComplexValue complexValue = propertyValue as ODataComplexValue; ProjectedPropertiesAnnotation emptyProjectedPropertiesMarker = null; if (!this.ShouldWritePropertyInContent(owningType, projectedProperties, propertyName, propertyValue, propertySourcePathSegment)) { if ((propertySourcePathSegment == null) || (complexValue == null)) { return(false); } emptyProjectedPropertiesMarker = ProjectedPropertiesAnnotation.EmptyProjectedPropertiesMarker; } WriterValidationUtils.ValidateProperty(property); duplicatePropertyNamesChecker.CheckForDuplicatePropertyNames(property); IEdmProperty edmProperty = WriterValidationUtils.ValidatePropertyDefined(propertyName, owningType); if (propertyValue is ODataStreamReferenceValue) { throw new ODataException(Microsoft.Data.OData.Strings.ODataWriter_StreamPropertiesMustBePropertiesOfODataEntry(propertyName)); } if (((edmProperty != null) && edmProperty.Type.IsSpatial()) || ((edmProperty == null) && (propertyValue is ISpatial))) { ODataVersionChecker.CheckSpatialValue(base.Version); } if (propertyValue == null) { this.WriteNullPropertyValue(edmProperty, propertyName, isTopLevel, isWritingCollection, beforePropertyAction); return(true); } bool isOpenPropertyType = ((owningType != null) && owningType.IsOpen) && (edmProperty == null); if (isOpenPropertyType) { ValidationUtils.ValidateOpenPropertyValue(propertyName, propertyValue); } IEdmTypeReference metadataTypeReference = (edmProperty == null) ? null : edmProperty.Type; if (complexValue != null) { DuplicatePropertyNamesChecker checker = base.CreateDuplicatePropertyNamesChecker(); if (isTopLevel) { this.WritePropertyStart(beforePropertyAction, propertyName, isWritingCollection, isTopLevel); this.WriteComplexValue(complexValue, metadataTypeReference, isOpenPropertyType, isWritingCollection, null, null, checker, null, epmValueCache, propertySourcePathSegment, null); this.WritePropertyEnd(); return(true); } if (beforeValueAction == null) { beforeValueAction = delegate { this.WritePropertyStart(beforePropertyAction, propertyName, isWritingCollection, isTopLevel); }; } if (afterValueAction == null) { afterValueAction = delegate { this.WritePropertyEnd(); }; } return(this.WriteComplexValue(complexValue, metadataTypeReference, isOpenPropertyType, isWritingCollection, beforeValueAction, afterValueAction, checker, null, epmValueCache, propertySourcePathSegment, emptyProjectedPropertiesMarker)); } ODataCollectionValue collectionValue = propertyValue as ODataCollectionValue; if (collectionValue != null) { ODataVersionChecker.CheckCollectionValueProperties(base.Version, propertyName); this.WritePropertyStart(beforePropertyAction, propertyName, isWritingCollection, isTopLevel); this.WriteCollectionValue(collectionValue, metadataTypeReference, isOpenPropertyType, isWritingCollection); this.WritePropertyEnd(); return(true); } this.WritePropertyStart(beforePropertyAction, propertyName, isWritingCollection, isTopLevel); this.WritePrimitiveValue(propertyValue, null, metadataTypeReference); this.WritePropertyEnd(); return(true); }
internal bool WriteComplexValue(ODataComplexValue complexValue, IEdmTypeReference metadataTypeReference, bool isOpenPropertyType, bool isWritingCollection, Action beforeValueAction, Action afterValueAction, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, CollectionWithoutExpectedTypeValidator collectionValidator, EpmValueCache epmValueCache, EpmSourcePathSegment epmSourcePathSegment, ProjectedPropertiesAnnotation projectedProperties) { Action action2 = null; string typeName = complexValue.TypeName; if (collectionValidator != null) { collectionValidator.ValidateCollectionItem(typeName, EdmTypeKind.Complex); } this.IncreaseRecursionDepth(); IEdmComplexTypeReference reference = WriterValidationUtils.ResolveTypeNameForWriting(base.Model, metadataTypeReference, ref typeName, EdmTypeKind.Complex, isOpenPropertyType).AsComplexOrNull(); if (((typeName != null) && (collectionValidator != null)) && (string.CompareOrdinal(collectionValidator.ItemTypeNameFromCollection, typeName) == 0)) { typeName = null; } SerializationTypeNameAnnotation annotation = complexValue.GetAnnotation <SerializationTypeNameAnnotation>(); if (annotation != null) { typeName = annotation.TypeName; } Action beforePropertiesAction = beforeValueAction; if (typeName != null) { if (beforeValueAction != null) { if (action2 == null) { action2 = delegate { beforeValueAction(); this.WritePropertyTypeAttribute(typeName); }; } beforePropertiesAction = action2; } else { this.WritePropertyTypeAttribute(typeName); } } if (((base.MessageWriterSettings.WriterBehavior != null) && base.MessageWriterSettings.WriterBehavior.UseV1ProviderBehavior) && !object.ReferenceEquals(projectedProperties, ProjectedPropertiesAnnotation.EmptyProjectedPropertiesMarker)) { IEdmComplexType definition = (IEdmComplexType)reference.Definition; if (base.Model.EpmCachedKeepPrimitiveInContent(definition) == null) { List <string> keptInContentPropertyNames = null; foreach (IEdmProperty property in from p in definition.Properties() where p.Type.IsODataPrimitiveTypeKind() select p) { EntityPropertyMappingAttribute entityPropertyMapping = EpmWriterUtils.GetEntityPropertyMapping(epmSourcePathSegment, property.Name); if ((entityPropertyMapping != null) && entityPropertyMapping.KeepInContent) { if (keptInContentPropertyNames == null) { keptInContentPropertyNames = new List <string>(); } keptInContentPropertyNames.Add(property.Name); } } base.Model.SetAnnotationValue <CachedPrimitiveKeepInContentAnnotation>(definition, new CachedPrimitiveKeepInContentAnnotation(keptInContentPropertyNames)); } } bool flag = this.WriteProperties((reference == null) ? null : reference.ComplexDefinition(), EpmValueCache.GetComplexValueProperties(epmValueCache, complexValue, true), isWritingCollection, beforePropertiesAction, afterValueAction, duplicatePropertyNamesChecker, epmValueCache, epmSourcePathSegment, projectedProperties); this.DecreaseRecursionDepth(); return(flag); }
internal void Add(EntityPropertyMappingInfo epmInfo) { string sourceName = epmInfo.Attribute.SourcePath; EpmSourcePathSegment currentProperty = this.Root; IList<EpmSourcePathSegment> activeSubProperties = currentProperty.SubProperties; EpmSourcePathSegment foundProperty = null; Debug.Assert(!string.IsNullOrEmpty(sourceName), "Must have been validated during EntityPropertyMappingAttribute construction"); foreach (string propertyName in sourceName.Split('/')) { if (propertyName.Length == 0) { throw new InvalidOperationException(Strings.EpmSourceTree_InvalidSourcePath(epmInfo.DefiningType.Name, sourceName)); } foundProperty = activeSubProperties.SingleOrDefault(e => e.PropertyName == propertyName); if (foundProperty != null) { currentProperty = foundProperty; } else { currentProperty = new EpmSourcePathSegment(propertyName); activeSubProperties.Add(currentProperty); } activeSubProperties = currentProperty.SubProperties; } if (foundProperty != null) { Debug.Assert(object.ReferenceEquals(foundProperty, currentProperty), "currentProperty variable should have been updated already to foundProperty"); if (foundProperty.EpmInfo.DefiningType.Name == epmInfo.DefiningType.Name) { throw new InvalidOperationException(Strings.EpmSourceTree_DuplicateEpmAttrsWithSameSourceName(epmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name)); } this.epmTargetTree.Remove(foundProperty.EpmInfo); } currentProperty.EpmInfo = epmInfo; this.epmTargetTree.Add(epmInfo); }
/// <summary>Validates the specified segment and all its subsegments.</summary> /// <param name="pathSegment">The path segment to validate.</param> /// <param name="resourceType">The resource type of the property represented by this segment (null for open properties).</param> private static void Validate(EpmSourcePathSegment pathSegment, ClientTypeOrResourceType_Alias resourceType)
private void WriteEntryContent(ODataEntry entry, IEdmEntityType entryType, EntryPropertiesValueCache propertiesValueCache, EpmSourcePathSegment rootSourcePathSegment, ProjectedPropertiesAnnotation projectedProperties) { ODataStreamReferenceValue mediaResource = entry.MediaResource; if (mediaResource == null) { this.atomOutputContext.XmlWriter.WriteStartElement("", "content", "http://www.w3.org/2005/Atom"); this.atomOutputContext.XmlWriter.WriteAttributeString("type", "application/xml"); this.atomEntryAndFeedSerializer.WriteProperties(entryType, propertiesValueCache.EntryProperties, false, new Action(this.atomEntryAndFeedSerializer.WriteEntryPropertiesStart), new Action(this.atomEntryAndFeedSerializer.WriteEntryPropertiesEnd), base.DuplicatePropertyNamesChecker, propertiesValueCache, rootSourcePathSegment, projectedProperties); this.atomOutputContext.XmlWriter.WriteEndElement(); } else { WriterValidationUtils.ValidateStreamReferenceValue(mediaResource, true); this.atomEntryAndFeedSerializer.WriteEntryMediaEditLink(mediaResource); if (mediaResource.ReadLink != null) { this.atomOutputContext.XmlWriter.WriteStartElement("", "content", "http://www.w3.org/2005/Atom"); this.atomOutputContext.XmlWriter.WriteAttributeString("type", mediaResource.ContentType); this.atomOutputContext.XmlWriter.WriteAttributeString("src", this.atomEntryAndFeedSerializer.UriToUrlAttributeValue(mediaResource.ReadLink)); this.atomOutputContext.XmlWriter.WriteEndElement(); } this.atomEntryAndFeedSerializer.WriteProperties(entryType, propertiesValueCache.EntryProperties, false, new Action(this.atomEntryAndFeedSerializer.WriteEntryPropertiesStart), new Action(this.atomEntryAndFeedSerializer.WriteEntryPropertiesEnd), base.DuplicatePropertyNamesChecker, propertiesValueCache, rootSourcePathSegment, projectedProperties); } }
internal bool WriteProperties(IEdmStructuredType owningType, IEnumerable<ODataProperty> cachedProperties, bool isWritingCollection, Action beforePropertiesAction, Action afterPropertiesAction, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, EpmValueCache epmValueCache, EpmSourcePathSegment epmSourcePathSegment, ProjectedPropertiesAnnotation projectedProperties) { if (cachedProperties == null) { return false; } bool flag = false; foreach (ODataProperty property in cachedProperties) { flag |= this.WriteProperty(property, owningType, false, isWritingCollection, flag ? null : beforePropertiesAction, epmValueCache, epmSourcePathSegment, duplicatePropertyNamesChecker, projectedProperties); } if ((afterPropertiesAction != null) && flag) { afterPropertiesAction(); } return flag; }
private bool WriteProperty(ODataProperty property, IEdmStructuredType owningType, bool isTopLevel, bool isWritingCollection, Action beforePropertyAction, EpmValueCache epmValueCache, EpmSourcePathSegment epmParentSourcePathSegment, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, ProjectedPropertiesAnnotation projectedProperties) { Action beforeValueAction = null; Action afterValueAction = null; WriterValidationUtils.ValidatePropertyNotNull(property); object propertyValue = property.Value; string propertyName = property.Name; EpmSourcePathSegment propertySourcePathSegment = EpmWriterUtils.GetPropertySourcePathSegment(epmParentSourcePathSegment, propertyName); ODataComplexValue complexValue = propertyValue as ODataComplexValue; ProjectedPropertiesAnnotation emptyProjectedPropertiesMarker = null; if (!this.ShouldWritePropertyInContent(owningType, projectedProperties, propertyName, propertyValue, propertySourcePathSegment)) { if ((propertySourcePathSegment == null) || (complexValue == null)) { return false; } emptyProjectedPropertiesMarker = ProjectedPropertiesAnnotation.EmptyProjectedPropertiesMarker; } WriterValidationUtils.ValidateProperty(property); duplicatePropertyNamesChecker.CheckForDuplicatePropertyNames(property); IEdmProperty edmProperty = WriterValidationUtils.ValidatePropertyDefined(propertyName, owningType); if (propertyValue is ODataStreamReferenceValue) { throw new ODataException(Microsoft.Data.OData.Strings.ODataWriter_StreamPropertiesMustBePropertiesOfODataEntry(propertyName)); } if (((edmProperty != null) && edmProperty.Type.IsSpatial()) || ((edmProperty == null) && (propertyValue is ISpatial))) { ODataVersionChecker.CheckSpatialValue(base.Version); } if (propertyValue == null) { this.WriteNullPropertyValue(edmProperty, propertyName, isTopLevel, isWritingCollection, beforePropertyAction); return true; } bool isOpenPropertyType = ((owningType != null) && owningType.IsOpen) && (edmProperty == null); if (isOpenPropertyType) { ValidationUtils.ValidateOpenPropertyValue(propertyName, propertyValue); } IEdmTypeReference metadataTypeReference = (edmProperty == null) ? null : edmProperty.Type; if (complexValue != null) { DuplicatePropertyNamesChecker checker = base.CreateDuplicatePropertyNamesChecker(); if (isTopLevel) { this.WritePropertyStart(beforePropertyAction, propertyName, isWritingCollection, isTopLevel); this.WriteComplexValue(complexValue, metadataTypeReference, isOpenPropertyType, isWritingCollection, null, null, checker, null, epmValueCache, propertySourcePathSegment, null); this.WritePropertyEnd(); return true; } if (beforeValueAction == null) { beforeValueAction = delegate { this.WritePropertyStart(beforePropertyAction, propertyName, isWritingCollection, isTopLevel); }; } if (afterValueAction == null) { afterValueAction = delegate { this.WritePropertyEnd(); }; } return this.WriteComplexValue(complexValue, metadataTypeReference, isOpenPropertyType, isWritingCollection, beforeValueAction, afterValueAction, checker, null, epmValueCache, propertySourcePathSegment, emptyProjectedPropertiesMarker); } ODataCollectionValue collectionValue = propertyValue as ODataCollectionValue; if (collectionValue != null) { ODataVersionChecker.CheckCollectionValueProperties(base.Version, propertyName); this.WritePropertyStart(beforePropertyAction, propertyName, isWritingCollection, isTopLevel); this.WriteCollectionValue(collectionValue, metadataTypeReference, isOpenPropertyType, isWritingCollection); this.WritePropertyEnd(); return true; } this.WritePropertyStart(beforePropertyAction, propertyName, isWritingCollection, isTopLevel); this.WritePrimitiveValue(propertyValue, null, metadataTypeReference); this.WritePropertyEnd(); return true; }
internal static void CacheEpmProperties(EntryPropertiesValueCache propertyValueCache, EpmSourceTree sourceTree) { EpmSourcePathSegment root = sourceTree.Root; CacheEpmSourcePathSegments(propertyValueCache, root.SubProperties, propertyValueCache.EntryProperties); }
/// <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> /// Write the items in a MultiValue in ATOM format. /// </summary> /// <param name="writer">The <see cref="XmlWriter"/> to write to.</param> /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param> /// <param name="multiValue">The MultiValue to write.</param> /// <param name="resourcePropertyType">The resource type of the multi value (or null if not metadata is available).</param> /// <param name="isOpenPropertyType">True if the type name belongs to an open property.</param> /// <param name="isWritingCollection">True if we are writing a collection instead of an entry.</param> /// <param name="version">The protocol version used for writing.</param> /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param> /// <param name="epmSourcePathSegment">The EPM source path segment which points to the multivalue property we're writing. (can be null)</param> private static void WriteMultiValue( XmlWriter writer, DataServiceMetadataProviderWrapper metadata, ODataMultiValue multiValue, ResourceType resourcePropertyType, bool isOpenPropertyType, bool isWritingCollection, ODataVersion version, EpmValueCache epmValueCache, EpmSourcePathSegment epmSourcePathSegment) { Debug.Assert(multiValue != null, "multiValue != null"); string typeName = multiValue.TypeName; // resolve the type name to the resource type; if no type name is specified we will use the // type inferred from metadata MultiValueResourceType multiValueType = (MultiValueResourceType)MetadataUtils.ResolveTypeName(metadata, resourcePropertyType, ref typeName, ResourceTypeKind.MultiValue, isOpenPropertyType); if (typeName != null) { WritePropertyTypeAttribute(writer, typeName); } ResourceType expectedItemType = multiValueType == null ? null : multiValueType.ItemType; IEnumerable items = EpmValueCache.GetMultiValueItems(epmValueCache, epmSourcePathSegment, multiValue, true); if (items != null) { foreach (object itemValue in items) { object item; EpmMultiValueItemCache epmItemCache = itemValue as EpmMultiValueItemCache; if (epmItemCache != null) { item = epmItemCache.ItemValue; } else { item = itemValue; } ValidationUtils.ValidateMultiValueItem(item); writer.WriteStartElement(AtomConstants.ODataNamespacePrefix, AtomConstants.ODataMultiValueItemElementName, AtomConstants.ODataNamespace); ODataComplexValue complexValue = item as ODataComplexValue; if (complexValue != null) { WriteComplexValue(writer, metadata, complexValue, expectedItemType, false, isWritingCollection, version, epmItemCache, epmSourcePathSegment); } else { ODataMultiValue multiValueItem = item as ODataMultiValue; if (multiValueItem != null) { throw new ODataException(Strings.ODataWriter_NestedMultiValuesAreNotSupported); } else { AtomValueUtils.WritePrimitiveValue(writer, item, expectedItemType); } } writer.WriteEndElement(); } } }
/// <summary> /// Writes a single property in ATOM format. /// </summary> /// <param name="property">The property to write out.</param> /// <param name="owningType">The type owning the property (or null if no metadata is available).</param> /// <param name="isTopLevel">true if writing a top-level property payload; otherwise false.</param> /// <param name="isWritingCollection">true if we are writing a collection instead of an entry.</param> /// <param name="beforePropertyAction">Action which is called before the property is written, if it's going to be written.</param> /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param> /// <param name="epmParentSourcePathSegment">The EPM source path segment which points to the property which sub-property we're writing. (can be null)</param> /// <param name="duplicatePropertyNamesChecker">The checker instance for duplicate property names.</param> /// <param name="projectedProperties">Set of projected properties, or null if all properties should be written.</param> /// <returns>true if the property was actually written, false otherwise.</returns> private bool WriteProperty( ODataProperty property, IEdmStructuredType owningType, bool isTopLevel, bool isWritingCollection, Action beforePropertyAction, EpmValueCache epmValueCache, EpmSourcePathSegment epmParentSourcePathSegment, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, ProjectedPropertiesAnnotation projectedProperties) { DebugUtils.CheckNoExternalCallers(); WriterValidationUtils.ValidatePropertyNotNull(property); object value = property.Value; string propertyName = property.Name; EpmSourcePathSegment epmSourcePathSegment = EpmWriterUtils.GetPropertySourcePathSegment(epmParentSourcePathSegment, propertyName); //// TODO: If we implement type conversions the value needs to be converted here //// since the next method call needs to know if the value is a string or not in some cases. ODataComplexValue complexValue = value as ODataComplexValue; ProjectedPropertiesAnnotation complexValueProjectedProperties = null; if (!this.ShouldWritePropertyInContent(owningType, projectedProperties, propertyName, value, epmSourcePathSegment)) { // If ShouldWritePropertyInContent returns false for a comlex value we have to continue // writing the property but set the projectedProperties to an empty array. The reason for this // is that we might find EPM on a nested property that has a null value and thus must be written // in content (in which case the parent property also has to be written). // This only applies if we have EPM information for the property. if (epmSourcePathSegment != null && complexValue != null) { Debug.Assert(!projectedProperties.IsPropertyProjected(propertyName), "ShouldWritePropertyInContent must not return false for a projected complex property."); complexValueProjectedProperties = ProjectedPropertiesAnnotation.EmptyProjectedPropertiesMarker; } else { return false; } } WriterValidationUtils.ValidateProperty(property); duplicatePropertyNamesChecker.CheckForDuplicatePropertyNames(property); IEdmProperty edmProperty = WriterValidationUtils.ValidatePropertyDefined(propertyName, owningType); if (value is ODataStreamReferenceValue) { throw new ODataException(o.Strings.ODataWriter_StreamPropertiesMustBePropertiesOfODataEntry(propertyName)); } // If the property is of Geography or Geometry type or the value is of Geography or Geometry type // make sure to check that the version is 3.0 or above. if ((edmProperty != null && edmProperty.Type.IsSpatial()) || (edmProperty == null && value is System.Spatial.ISpatial)) { ODataVersionChecker.CheckSpatialValue(this.Version); } // Null property value. if (value == null) { this.WriteNullPropertyValue(edmProperty, propertyName, isTopLevel, isWritingCollection, beforePropertyAction); return true; } bool isOpenPropertyType = owningType != null && owningType.IsOpen && edmProperty == null; if (isOpenPropertyType) { ValidationUtils.ValidateOpenPropertyValue(propertyName, value); } IEdmTypeReference propertyTypeReference = edmProperty == null ? null : edmProperty.Type; if (complexValue != null) { // Complex properties are written recursively. DuplicatePropertyNamesChecker complexValuePropertyNamesChecker = this.CreateDuplicatePropertyNamesChecker(); if (isTopLevel) { // Top-level property must always write the property element Debug.Assert(complexValueProjectedProperties == null, "complexValueProjectedProperties == null"); this.WritePropertyStart(beforePropertyAction, propertyName, isWritingCollection, isTopLevel); this.AssertRecursionDepthIsZero(); this.WriteComplexValue( complexValue, propertyTypeReference, isOpenPropertyType, isWritingCollection, null /* beforeValueAction */, null /* afterValueAction */, complexValuePropertyNamesChecker, null /* collectionValidator */, epmValueCache, epmSourcePathSegment, null /* projectedProperties */); this.AssertRecursionDepthIsZero(); this.WritePropertyEnd(); return true; } return this.WriteComplexValue( complexValue, propertyTypeReference, isOpenPropertyType, isWritingCollection, () => this.WritePropertyStart(beforePropertyAction, propertyName, isWritingCollection, isTopLevel), () => this.WritePropertyEnd(), complexValuePropertyNamesChecker, null /* collectionValidator */, epmValueCache, epmSourcePathSegment, complexValueProjectedProperties); } ODataCollectionValue collectionValue = value as ODataCollectionValue; if (collectionValue != null) { ODataVersionChecker.CheckCollectionValueProperties(this.Version, propertyName); this.WritePropertyStart(beforePropertyAction, propertyName, isWritingCollection, isTopLevel); this.WriteCollectionValue( collectionValue, propertyTypeReference, isOpenPropertyType, isWritingCollection); this.WritePropertyEnd(); return true; } this.WritePropertyStart(beforePropertyAction, propertyName, isWritingCollection, isTopLevel); this.WritePrimitiveValue(value, /*collectionValidator*/ null, propertyTypeReference); this.WritePropertyEnd(); return true; }
/// <summary>Default constructor creates a null root</summary> /// <param name="epmTargetTree">Target xml tree</param> internal EpmSourceTree(EpmTargetTree epmTargetTree) { this.root = new EpmSourcePathSegment(); this.epmTargetTree = epmTargetTree; }
/// <summary> /// Validates the specified segment and all its subsegments. /// </summary> /// <param name="pathSegment">The path segment to validate.</param> /// <param name="type">The type of the property represented by this segment (null for open properties).</param> private static void Validate(EpmSourcePathSegment pathSegment, IEdmType type) { Debug.Assert(pathSegment != null, "pathSegment != null"); foreach (EpmSourcePathSegment subSegment in pathSegment.SubProperties) { IEdmTypeReference subSegmentTypeReference = GetPropertyType(type, subSegment.PropertyName); IEdmType subSegmentType = subSegmentTypeReference == null ? null : subSegmentTypeReference.Definition; Validate(subSegment, subSegmentType); } }
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); }
internal bool WriteProperties(IEdmStructuredType owningType, IEnumerable <ODataProperty> cachedProperties, bool isWritingCollection, Action beforePropertiesAction, Action afterPropertiesAction, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, EpmValueCache epmValueCache, EpmSourcePathSegment epmSourcePathSegment, ProjectedPropertiesAnnotation projectedProperties) { if (cachedProperties == null) { return(false); } bool flag = false; foreach (ODataProperty property in cachedProperties) { flag |= this.WriteProperty(property, owningType, false, isWritingCollection, flag ? null : beforePropertiesAction, epmValueCache, epmSourcePathSegment, duplicatePropertyNamesChecker, projectedProperties); } if ((afterPropertiesAction != null) && flag) { afterPropertiesAction(); } return(flag); }
/// <summary> /// Writes a single property in ATOM format. /// </summary> /// <param name="property">The property to write out.</param> /// <param name="owningType">The type owning the property (or null if no metadata is available).</param> /// <param name="isTopLevel">true if writing a top-level property payload; otherwise false.</param> /// <param name="isWritingCollection">true if we are writing a collection instead of an entry.</param> /// <param name="beforePropertyAction">Action which is called before the property is written, if it's going to be written.</param> /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param> /// <param name="epmParentSourcePathSegment">The EPM source path segment which points to the property which sub-property we're writing. (can be null)</param> /// <param name="duplicatePropertyNamesChecker">The checker instance for duplicate property names.</param> /// <param name="projectedProperties">Set of projected properties, or null if all properties should be written.</param> /// <returns>true if the property was actually written, false otherwise.</returns> private bool WriteProperty( ODataProperty property, IEdmStructuredType owningType, bool isTopLevel, bool isWritingCollection, Action beforePropertyAction, EpmValueCache epmValueCache, EpmSourcePathSegment epmParentSourcePathSegment, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, ProjectedPropertiesAnnotation projectedProperties) { DebugUtils.CheckNoExternalCallers(); WriterValidationUtils.ValidatePropertyNotNull(property); object value = property.Value; string propertyName = property.Name; EpmSourcePathSegment epmSourcePathSegment = EpmWriterUtils.GetPropertySourcePathSegment(epmParentSourcePathSegment, propertyName); //// TODO: If we implement type conversions the value needs to be converted here //// since the next method call needs to know if the value is a string or not in some cases. ODataComplexValue complexValue = value as ODataComplexValue; ProjectedPropertiesAnnotation complexValueProjectedProperties = null; if (!this.ShouldWritePropertyInContent(owningType, projectedProperties, propertyName, value, epmSourcePathSegment)) { // If ShouldWritePropertyInContent returns false for a comlex value we have to continue // writing the property but set the projectedProperties to an empty array. The reason for this // is that we might find EPM on a nested property that has a null value and thus must be written // in content (in which case the parent property also has to be written). // This only applies if we have EPM information for the property. if (epmSourcePathSegment != null && complexValue != null) { Debug.Assert(!projectedProperties.IsPropertyProjected(propertyName), "ShouldWritePropertyInContent must not return false for a projected complex property."); complexValueProjectedProperties = ProjectedPropertiesAnnotation.EmptyProjectedPropertiesMarker; } else { return(false); } } WriterValidationUtils.ValidateProperty(property); duplicatePropertyNamesChecker.CheckForDuplicatePropertyNames(property); IEdmProperty edmProperty = WriterValidationUtils.ValidatePropertyDefined(propertyName, owningType); if (value is ODataStreamReferenceValue) { throw new ODataException(o.Strings.ODataWriter_StreamPropertiesMustBePropertiesOfODataEntry(propertyName)); } // If the property is of Geography or Geometry type or the value is of Geography or Geometry type // make sure to check that the version is 3.0 or above. if ((edmProperty != null && edmProperty.Type.IsSpatial()) || (edmProperty == null && value is System.Spatial.ISpatial)) { ODataVersionChecker.CheckSpatialValue(this.Version); } // Null property value. if (value == null) { this.WriteNullPropertyValue(edmProperty, propertyName, isTopLevel, isWritingCollection, beforePropertyAction); return(true); } bool isOpenPropertyType = owningType != null && owningType.IsOpen && edmProperty == null; if (isOpenPropertyType) { ValidationUtils.ValidateOpenPropertyValue(propertyName, value); } IEdmTypeReference propertyTypeReference = edmProperty == null ? null : edmProperty.Type; if (complexValue != null) { // Complex properties are written recursively. DuplicatePropertyNamesChecker complexValuePropertyNamesChecker = this.CreateDuplicatePropertyNamesChecker(); if (isTopLevel) { // Top-level property must always write the property element Debug.Assert(complexValueProjectedProperties == null, "complexValueProjectedProperties == null"); this.WritePropertyStart(beforePropertyAction, propertyName, isWritingCollection, isTopLevel); this.AssertRecursionDepthIsZero(); this.WriteComplexValue( complexValue, propertyTypeReference, isOpenPropertyType, isWritingCollection, null /* beforeValueAction */, null /* afterValueAction */, complexValuePropertyNamesChecker, null /* collectionValidator */, epmValueCache, epmSourcePathSegment, null /* projectedProperties */); this.AssertRecursionDepthIsZero(); this.WritePropertyEnd(); return(true); } return(this.WriteComplexValue( complexValue, propertyTypeReference, isOpenPropertyType, isWritingCollection, () => this.WritePropertyStart(beforePropertyAction, propertyName, isWritingCollection, isTopLevel), () => this.WritePropertyEnd(), complexValuePropertyNamesChecker, null /* collectionValidator */, epmValueCache, epmSourcePathSegment, complexValueProjectedProperties)); } ODataCollectionValue collectionValue = value as ODataCollectionValue; if (collectionValue != null) { ODataVersionChecker.CheckCollectionValueProperties(this.Version, propertyName); this.WritePropertyStart(beforePropertyAction, propertyName, isWritingCollection, isTopLevel); this.WriteCollectionValue( collectionValue, propertyTypeReference, isOpenPropertyType, isWritingCollection); this.WritePropertyEnd(); return(true); } this.WritePropertyStart(beforePropertyAction, propertyName, isWritingCollection, isTopLevel); this.WritePrimitiveValue(value, /*collectionValidator*/ null, propertyTypeReference); this.WritePropertyEnd(); return(true); }
private bool ShouldWritePropertyInContent(IEdmStructuredType owningType, ProjectedPropertiesAnnotation projectedProperties, string propertyName, object propertyValue, EpmSourcePathSegment epmSourcePathSegment) { bool flag = !projectedProperties.ShouldSkipProperty(propertyName); if ((((base.MessageWriterSettings.WriterBehavior != null) && base.MessageWriterSettings.WriterBehavior.UseV1ProviderBehavior) && (owningType != null)) && owningType.IsODataComplexTypeKind()) { IEdmComplexType complexType = (IEdmComplexType)owningType; CachedPrimitiveKeepInContentAnnotation annotation = base.Model.EpmCachedKeepPrimitiveInContent(complexType); if ((annotation != null) && annotation.IsKeptInContent(propertyName)) { return(flag); } } if ((propertyValue == null) && (epmSourcePathSegment != null)) { return(true); } EntityPropertyMappingAttribute entityPropertyMapping = EpmWriterUtils.GetEntityPropertyMapping(epmSourcePathSegment); if (entityPropertyMapping == null) { return(flag); } string str = propertyValue as string; if ((str != null) && (str.Length == 0)) { switch (entityPropertyMapping.TargetSyndicationItem) { case SyndicationItemProperty.AuthorEmail: case SyndicationItemProperty.AuthorUri: case SyndicationItemProperty.ContributorEmail: case SyndicationItemProperty.ContributorUri: return(true); } } return(entityPropertyMapping.KeepInContent && flag); }
/// <summary> /// Sets path to the source property. /// </summary> /// <param name="path">The path as an array of source path segments.</param> internal void SetPropertyValuePath(EpmSourcePathSegment[] path) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(path != null, "path != null"); Debug.Assert( this.multiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueItemProperty || path.Length > 0, "The path must contain at least one segment unless it's a multivalue item path."); Debug.Assert(this.propertyValuePath == null, "The property value path was already initialized."); this.propertyValuePath = path; }
/// <summary> /// Validates the specified segment and all its subsegments. /// </summary> /// <param name="pathSegment">The path segment to validate.</param> /// <param name="resourceType">The resource type of the property represented by this segment (null for open properties).</param> private static void Validate(EpmSourcePathSegment pathSegment, ResourceType resourceType) { Debug.Assert(pathSegment != null, "pathSegment != null"); foreach (EpmSourcePathSegment subSegment in pathSegment.SubProperties) { bool isMultiValueProperty; ResourceType subSegmentResourceType = GetPropertyType(resourceType, subSegment.PropertyName, out isMultiValueProperty); if (isMultiValueProperty) { ValidateMultiValueSegment(subSegment.EpmInfo, subSegment, subSegmentResourceType); } else { Validate(subSegment, subSegmentResourceType); } } }
internal bool WriteComplexValue(ODataComplexValue complexValue, IEdmTypeReference metadataTypeReference, bool isOpenPropertyType, bool isWritingCollection, Action beforeValueAction, Action afterValueAction, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, CollectionWithoutExpectedTypeValidator collectionValidator, EpmValueCache epmValueCache, EpmSourcePathSegment epmSourcePathSegment, ProjectedPropertiesAnnotation projectedProperties) { Action action2 = null; string typeName = complexValue.TypeName; if (collectionValidator != null) { collectionValidator.ValidateCollectionItem(typeName, EdmTypeKind.Complex); } this.IncreaseRecursionDepth(); IEdmComplexTypeReference reference = WriterValidationUtils.ResolveTypeNameForWriting(base.Model, metadataTypeReference, ref typeName, EdmTypeKind.Complex, isOpenPropertyType).AsComplexOrNull(); if (((typeName != null) && (collectionValidator != null)) && (string.CompareOrdinal(collectionValidator.ItemTypeNameFromCollection, typeName) == 0)) { typeName = null; } SerializationTypeNameAnnotation annotation = complexValue.GetAnnotation<SerializationTypeNameAnnotation>(); if (annotation != null) { typeName = annotation.TypeName; } Action beforePropertiesAction = beforeValueAction; if (typeName != null) { if (beforeValueAction != null) { if (action2 == null) { action2 = delegate { beforeValueAction(); this.WritePropertyTypeAttribute(typeName); }; } beforePropertiesAction = action2; } else { this.WritePropertyTypeAttribute(typeName); } } if (((base.MessageWriterSettings.WriterBehavior != null) && base.MessageWriterSettings.WriterBehavior.UseV1ProviderBehavior) && !object.ReferenceEquals(projectedProperties, ProjectedPropertiesAnnotation.EmptyProjectedPropertiesMarker)) { IEdmComplexType definition = (IEdmComplexType) reference.Definition; if (base.Model.EpmCachedKeepPrimitiveInContent(definition) == null) { List<string> keptInContentPropertyNames = null; foreach (IEdmProperty property in from p in definition.Properties() where p.Type.IsODataPrimitiveTypeKind() select p) { EntityPropertyMappingAttribute entityPropertyMapping = EpmWriterUtils.GetEntityPropertyMapping(epmSourcePathSegment, property.Name); if ((entityPropertyMapping != null) && entityPropertyMapping.KeepInContent) { if (keptInContentPropertyNames == null) { keptInContentPropertyNames = new List<string>(); } keptInContentPropertyNames.Add(property.Name); } } base.Model.SetAnnotationValue<CachedPrimitiveKeepInContentAnnotation>(definition, new CachedPrimitiveKeepInContentAnnotation(keptInContentPropertyNames)); } } bool flag = this.WriteProperties((reference == null) ? null : reference.ComplexDefinition(), EpmValueCache.GetComplexValueProperties(epmValueCache, complexValue, true), isWritingCollection, beforePropertiesAction, afterValueAction, duplicatePropertyNamesChecker, epmValueCache, epmSourcePathSegment, projectedProperties); this.DecreaseRecursionDepth(); return flag; }
/// <summary> /// Determines if the property with the specified value should be written into content or not. /// </summary> /// <param name="owningType">The owning type of the property to be checked.</param> /// <param name="projectedProperties">The set of projected properties for the <paramref name="owningType"/></param> /// <param name="propertyName">The name of the property to be checked.</param> /// <param name="propertyValue">The property value to write.</param> /// <param name="epmSourcePathSegment">The EPM source path segment for the property being written.</param> /// <returns>true if the property should be written into content, or false otherwise</returns> private bool ShouldWritePropertyInContent( IEdmStructuredType owningType, ProjectedPropertiesAnnotation projectedProperties, string propertyName, object propertyValue, EpmSourcePathSegment epmSourcePathSegment) { // check whether the property is projected; if no EPM is specified for the property the projection decides bool propertyProjected = !projectedProperties.ShouldSkipProperty(propertyName); bool useV1ProviderBehavior = this.MessageWriterSettings.WriterBehavior == null ? false : this.MessageWriterSettings.WriterBehavior.UseV1ProviderBehavior; if (useV1ProviderBehavior && owningType != null && owningType.IsODataComplexTypeKind()) { IEdmComplexType owningComplexType = (IEdmComplexType)owningType; CachedPrimitiveKeepInContentAnnotation keepInContentAnnotation = this.Model.EpmCachedKeepPrimitiveInContent(owningComplexType); if (keepInContentAnnotation != null && keepInContentAnnotation.IsKeptInContent(propertyName)) { return propertyProjected; } } // We sometimes write properties into content even if asked not to. // If the property value is null and the property (or one of its descendant properties) is mapped, // we always write into content, even if the property was not projected. if (propertyValue == null && epmSourcePathSegment != null) { return true; } EntityPropertyMappingAttribute entityPropertyMapping = EpmWriterUtils.GetEntityPropertyMapping(epmSourcePathSegment); if (entityPropertyMapping == null) { return propertyProjected; } string stringPropertyValue = propertyValue as string; if (stringPropertyValue != null && stringPropertyValue.Length == 0) { // If the property value is an empty string and we should be writing it into an ATOM element which does not allow empty string // we write it into content as well, also even if the property was not projected. switch (entityPropertyMapping.TargetSyndicationItem) { case SyndicationItemProperty.AuthorEmail: case SyndicationItemProperty.AuthorUri: case SyndicationItemProperty.ContributorEmail: case SyndicationItemProperty.ContributorUri: return true; default: break; } } return entityPropertyMapping.KeepInContent && propertyProjected; }
/// <summary> /// Writes a single property in ATOM format. /// </summary> /// <param name="property">The property to write out.</param> /// <param name="owningType">The owning type for the <paramref name="property"/> or null if no metadata is available.</param> /// <param name="isTopLevel">true if writing a top-level property payload; otherwise false.</param> /// <param name="isWritingCollection">true if we are writing a top-level collection instead of an entry.</param> /// <param name="beforePropertyAction">Action which is called before the property is written, if it's going to be written.</param> /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param> /// <param name="epmParentSourcePathSegment">The EPM source path segment which points to the property which sub-property we're writing. (can be null)</param> /// <param name="duplicatePropertyNamesChecker">The checker instance for duplicate property names.</param> /// <param name="projectedProperties">Set of projected properties, or null if all properties should be written.</param> /// <returns>true if the property was actually written, false otherwise.</returns> private bool WriteProperty( ODataProperty property, IEdmStructuredType owningType, bool isTopLevel, bool isWritingCollection, Action beforePropertyAction, EpmValueCache epmValueCache, EpmSourcePathSegment epmParentSourcePathSegment, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, ProjectedPropertiesAnnotation projectedProperties) { DebugUtils.CheckNoExternalCallers(); WriterValidationUtils.ValidatePropertyNotNull(property); object value = property.Value; string propertyName = property.Name; EpmSourcePathSegment epmSourcePathSegment = EpmWriterUtils.GetPropertySourcePathSegment(epmParentSourcePathSegment, propertyName); //// TODO: If we implement type conversions the value needs to be converted here //// since the next method call needs to know if the value is a string or not in some cases. ODataComplexValue complexValue = value as ODataComplexValue; ProjectedPropertiesAnnotation complexValueProjectedProperties = null; if (!this.ShouldWritePropertyInContent(owningType, projectedProperties, propertyName, value, epmSourcePathSegment)) { // If ShouldWritePropertyInContent returns false for a comlex value we have to continue // writing the property but set the projectedProperties to an empty array. The reason for this // is that we might find EPM on a nested property that has a null value and thus must be written // in content (in which case the parent property also has to be written). // This only applies if we have EPM information for the property. if (epmSourcePathSegment != null && complexValue != null) { Debug.Assert(!projectedProperties.IsPropertyProjected(propertyName), "ShouldWritePropertyInContent must not return false for a projected complex property."); complexValueProjectedProperties = ProjectedPropertiesAnnotation.EmptyProjectedPropertiesInstance; } else { return(false); } } WriterValidationUtils.ValidatePropertyName(propertyName); duplicatePropertyNamesChecker.CheckForDuplicatePropertyNames(property); IEdmProperty edmProperty = WriterValidationUtils.ValidatePropertyDefined(propertyName, owningType, this.MessageWriterSettings.UndeclaredPropertyBehaviorKinds); IEdmTypeReference propertyTypeReference = edmProperty == null ? null : edmProperty.Type; if (value is ODataStreamReferenceValue) { throw new ODataException(ODataErrorStrings.ODataWriter_StreamPropertiesMustBePropertiesOfODataEntry(propertyName)); } // If the property is of Geography or Geometry type or the value is of Geography or Geometry type // make sure to check that the version is 3.0 or above. if ((propertyTypeReference != null && propertyTypeReference.IsSpatial()) || (propertyTypeReference == null && value is System.Spatial.ISpatial)) { ODataVersionChecker.CheckSpatialValue(this.Version); } // Null property value. if (value == null) { this.WriteNullPropertyValue(propertyTypeReference, propertyName, isTopLevel, isWritingCollection, beforePropertyAction); return(true); } bool isOpenPropertyType = owningType != null && owningType.IsOpen && propertyTypeReference == null; if (isOpenPropertyType) { ValidationUtils.ValidateOpenPropertyValue(propertyName, value, this.MessageWriterSettings.UndeclaredPropertyBehaviorKinds); } if (complexValue != null) { return(this.WriteComplexValueProperty( complexValue, propertyName, isTopLevel, isWritingCollection, beforePropertyAction, epmValueCache, propertyTypeReference, isOpenPropertyType, epmSourcePathSegment, complexValueProjectedProperties)); } ODataCollectionValue collectionValue = value as ODataCollectionValue; if (collectionValue != null) { this.WriteCollectionValueProperty( collectionValue, propertyName, isTopLevel, isWritingCollection, beforePropertyAction, propertyTypeReference, isOpenPropertyType); return(true); } // If the value isn't one of the value types tested for already, it must be a non-null primitive. this.WritePropertyStart(beforePropertyAction, propertyName, isWritingCollection, isTopLevel); SerializationTypeNameAnnotation serializationTypeNameAnnotation = property.ODataValue.GetAnnotation <SerializationTypeNameAnnotation>(); this.WritePrimitiveValue(value, /*collectionValidator*/ null, propertyTypeReference, serializationTypeNameAnnotation); this.WritePropertyEnd(); return(true); }
/// <summary> /// Write the given collection of properties. /// </summary> /// <param name="writer">The <see cref="XmlWriter"/> to write to.</param> /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param> /// <param name="owningType">The <see cref="ResourceType"/> of the entry (or null if not metadata is available).</param> /// <param name="cachedProperties">Collection of cached properties for the entry.</param> /// <param name="version">The protocol version used for writing.</param> /// <param name="isWritingCollection">True if we are writing a collection instead of an entry.</param> /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param> /// <param name="epmSourcePathSegment">The EPM source path segment which points to the property which sub-properites we're writing. (can be null)</param> internal static void WriteProperties( XmlWriter writer, DataServiceMetadataProviderWrapper metadata, ResourceType owningType, IEnumerable<ODataProperty> cachedProperties, ODataVersion version, bool isWritingCollection, EpmValueCache epmValueCache, EpmSourcePathSegment epmSourcePathSegment) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(writer != null, "writer != null"); if (cachedProperties == null) { return; } foreach (ODataProperty property in cachedProperties) { WriteProperty(writer, metadata, property, owningType, version, false, isWritingCollection, epmValueCache, epmSourcePathSegment); } }
/// <summary> /// Write the given collection of properties. /// </summary> /// <param name="owningType">The <see cref="IEdmStructuredType"/> of the entry (or null if not metadata is available).</param> /// <param name="cachedProperties">Collection of cached properties for the entry.</param> /// <param name="isWritingCollection">true if we are writing a collection instead of an entry.</param> /// <param name="beforePropertiesAction">Action which is called before the properties are written, if there are any property.</param> /// <param name="afterPropertiesAction">Action which is called after the properties are written, if there are any property.</param> /// <param name="duplicatePropertyNamesChecker">The checker instance for duplicate property names.</param> /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param> /// <param name="epmSourcePathSegment">The EPM source path segment which points to the property which sub-properites we're writing. (can be null)</param> /// <param name="projectedProperties">Set of projected properties, or null if all properties should be written.</param> /// <returns>true if anything was written, false otherwise.</returns> internal bool WriteProperties( IEdmStructuredType owningType, IEnumerable<ODataProperty> cachedProperties, bool isWritingCollection, Action beforePropertiesAction, Action afterPropertiesAction, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, EpmValueCache epmValueCache, EpmSourcePathSegment epmSourcePathSegment, ProjectedPropertiesAnnotation projectedProperties) { DebugUtils.CheckNoExternalCallers(); if (cachedProperties == null) { return false; } bool propertyWritten = false; foreach (ODataProperty property in cachedProperties) { propertyWritten |= this.WriteProperty( property, owningType, false, isWritingCollection, propertyWritten ? null : beforePropertiesAction, epmValueCache, epmSourcePathSegment, duplicatePropertyNamesChecker, projectedProperties); } if (afterPropertiesAction != null && propertyWritten) { afterPropertiesAction(); } return propertyWritten; }
/// <summary> /// Writes out the value of a complex property. /// </summary> /// <param name="writer">The <see cref="XmlWriter"/> to write to.</param> /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param> /// <param name="complexValue">The complex value to write.</param> /// <param name="metadataType">The metadata type for the complex value.</param> /// <param name="isOpenPropertyType">True if the type name belongs to an open property.</param> /// <param name="isWritingCollection">True if we are writing a collection instead of an entry.</param> /// <param name="version">The protocol version used for writing.</param> /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param> /// <param name="epmSourcePathSegment">The EPM source path segment which points to the property we're writing. (can be null)</param> internal static void WriteComplexValue( XmlWriter writer, DataServiceMetadataProviderWrapper metadata, ODataComplexValue complexValue, ResourceType metadataType, bool isOpenPropertyType, bool isWritingCollection, ODataVersion version, EpmValueCache epmValueCache, EpmSourcePathSegment epmSourcePathSegment) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(complexValue != null, "complexValue != null"); string typeName = complexValue.TypeName; // resolve the type name to the resource type; if no type name is specified we will use the // type inferred from metadata ResourceType complexValueType = MetadataUtils.ResolveTypeName(metadata, metadataType, ref typeName, ResourceTypeKind.ComplexType, isOpenPropertyType); if (typeName != null) { WritePropertyTypeAttribute(writer, typeName); } WriteProperties( writer, metadata, complexValueType, EpmValueCache.GetComplexValueProperties(epmValueCache, epmSourcePathSegment, complexValue, true), version, isWritingCollection, epmValueCache, epmSourcePathSegment); }
internal EpmSourceTree(EpmTargetTree epmTargetTree) { this.root = new EpmSourcePathSegment(string.Empty); this.epmTargetTree = epmTargetTree; }
/// <summary> /// Determines if the property with the specified value should be written into content or not. /// </summary> /// <param name="propertyValue">The property value to write.</param> /// <param name="epmSourcePathSegment">The EPM source path segment for the property being written.</param> /// <param name="version">The version of the protocol being used for the response.</param> /// <returns>true if the property should be written into content, or false otherwise</returns> private static bool ShouldWritePropertyInContent(object propertyValue, EpmSourcePathSegment epmSourcePathSegment, ODataVersion version) { if (epmSourcePathSegment == null) { return true; } EntityPropertyMappingInfo epmInfo = epmSourcePathSegment.EpmInfo; if (epmInfo == null) { return true; } EntityPropertyMappingAttribute epmAttribute = epmInfo.Attribute; Debug.Assert(epmAttribute != null, "Attribute should always be initialized for EpmInfo."); if (version <= ODataVersion.V2) { // In V2 and lower we sometimes write properties into content even if asked not to. // If the property value is null, we always write into content if (propertyValue == null) { return true; } string stringPropertyValue = propertyValue as string; if (stringPropertyValue != null && stringPropertyValue.Length == 0) { // If the property value is an empty string and we should be writing it into an ATOM element which does not allow empty string // we write it into content as well. switch (epmAttribute.TargetSyndicationItem) { case SyndicationItemProperty.AuthorEmail: case SyndicationItemProperty.AuthorUri: case SyndicationItemProperty.ContributorEmail: case SyndicationItemProperty.ContributorUri: return true; default: break; } } } return epmAttribute.KeepInContent; }
/// <summary>Writes the value of a complex object.</summary> /// <param name="element">Element to write.</param> /// <param name="propertyName">name of the property whose value needs to be written</param> /// <param name="expectedType">expected type of the property</param> /// <param name="relativeUri">relative uri for the complex type element</param> /// <param name="content">Content to write to.</param> /// <param name="currentSourceRoot">Epm source sub-tree corresponding to <paramref name="element"/></param> private void WriteComplexObjectValue(object element, string propertyName, ResourceType expectedType, string relativeUri, DictionaryContent content, EpmSourcePathSegment currentSourceRoot) { Debug.Assert(!String.IsNullOrEmpty(propertyName), "!String.IsNullOrEmpty(propertyName)"); Debug.Assert(expectedType != null, "expectedType != null"); Debug.Assert(!String.IsNullOrEmpty(relativeUri), "!String.IsNullOrEmpty(relativeUri)"); Debug.Assert(expectedType.ResourceTypeKind == ResourceTypeKind.ComplexType, "Must be complex type"); Debug.Assert(content != null, "content != null"); // Non-value complex types may form a cycle. // PERF: we can keep a single element around and save the HashSet initialization // until we find a second complex type - this saves the allocation on trees // with shallow (single-level) complex types. Debug.Assert(!expectedType.IsMediaLinkEntry, "!expectedType.IsMediaLinkEntry"); DictionaryContent valueProperties = new DictionaryContent(expectedType.Properties.Count); Debug.Assert(!expectedType.InstanceType.IsValueType, "!expectedType.Type.IsValueType -- checked in the resource type constructor."); if (element == null) { content.AddNull(expectedType.FullName, propertyName); } else { if (this.AddToComplexTypeCollection(element)) { ResourceType resourceType = WebUtil.GetNonPrimitiveResourceType(this.Provider, element); this.WriteObjectProperties(null, element, resourceType, null, relativeUri, null, valueProperties, currentSourceRoot); if (!valueProperties.IsEmpty) { content.Add(propertyName, resourceType.FullName, valueProperties); } this.RemoveFromComplexTypeCollection(element); } else { throw new InvalidOperationException(Strings.Serializer_LoopsNotAllowedInComplexTypes(propertyName)); } } }
internal bool WriteComplexValue( ODataComplexValue complexValue, IEdmTypeReference metadataTypeReference, bool isOpenPropertyType, bool isWritingCollection, Action beforeValueAction, Action afterValueAction, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, CollectionWithoutExpectedTypeValidator collectionValidator, EpmValueCache epmValueCache, EpmSourcePathSegment epmSourcePathSegment, ProjectedPropertiesAnnotation projectedProperties) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(complexValue != null, "complexValue != null"); string typeName = complexValue.TypeName; if (collectionValidator != null) { collectionValidator.ValidateCollectionItem(typeName, EdmTypeKind.Complex); } this.IncreaseRecursionDepth(); // resolve the type name to the type; if no type name is specified we will use the // type inferred from metadata IEdmComplexTypeReference complexTypeReference = WriterValidationUtils.ResolveTypeNameForWriting(this.Model, metadataTypeReference, ref typeName, EdmTypeKind.Complex, isOpenPropertyType).AsComplexOrNull(); // If the type is the same as the one specified by the parent collection, omit the type name, since it's not needed. if (typeName != null && collectionValidator != null) { string expectedItemTypeName = collectionValidator.ItemTypeNameFromCollection; if (string.CompareOrdinal(expectedItemTypeName, typeName) == 0) { typeName = null; } } SerializationTypeNameAnnotation serializationTypeNameAnnotation = complexValue.GetAnnotation<SerializationTypeNameAnnotation>(); if (serializationTypeNameAnnotation != null) { typeName = serializationTypeNameAnnotation.TypeName; } Action beforeValueCallbackWithTypeName = beforeValueAction; if (typeName != null) { // The beforeValueAction (if specified) will write the actual property element start. // So if we are to write the type attribute, we must postpone that after the start element was written. // And so we chain the existing action with our type attribute writing and use that // as the before action instead. if (beforeValueAction != null) { beforeValueCallbackWithTypeName = () => { beforeValueAction(); this.WritePropertyTypeAttribute(typeName); }; } else { this.WritePropertyTypeAttribute(typeName); } } // NOTE: see the comments on ODataWriterBehavior.UseV1ProviderBehavior for more information // NOTE: We have to check for ProjectedPropertiesAnnotation.Empty here to avoid filling the cache for // complex values we are writing only to ensure we don't have nested EPM-mapped null values // that will end up in the content eventually. if (this.MessageWriterSettings.WriterBehavior != null && this.MessageWriterSettings.WriterBehavior.UseV1ProviderBehavior && !object.ReferenceEquals(projectedProperties, ProjectedPropertiesAnnotation.EmptyProjectedPropertiesMarker)) { IEdmComplexType complexType = (IEdmComplexType)complexTypeReference.Definition; CachedPrimitiveKeepInContentAnnotation keepInContentCache = this.Model.EpmCachedKeepPrimitiveInContent(complexType); if (keepInContentCache == null) { // we are about to write the first value of the given type; compute the keep-in-content information for the primitive properties of this type. List<string> keepInContentPrimitiveProperties = null; // initialize the cache with all primitive properties foreach (IEdmProperty edmProperty in complexType.Properties().Where(p => p.Type.IsODataPrimitiveTypeKind())) { // figure out the keep-in-content value EntityPropertyMappingAttribute entityPropertyMapping = EpmWriterUtils.GetEntityPropertyMapping(epmSourcePathSegment, edmProperty.Name); if (entityPropertyMapping != null && entityPropertyMapping.KeepInContent) { if (keepInContentPrimitiveProperties == null) { keepInContentPrimitiveProperties = new List<string>(); } keepInContentPrimitiveProperties.Add(edmProperty.Name); } } this.Model.SetAnnotationValue<CachedPrimitiveKeepInContentAnnotation>(complexType, new CachedPrimitiveKeepInContentAnnotation(keepInContentPrimitiveProperties)); } } bool propertyWritten = this.WriteProperties( complexTypeReference == null ? null : complexTypeReference.ComplexDefinition(), EpmValueCache.GetComplexValueProperties(epmValueCache, complexValue, true), isWritingCollection, beforeValueCallbackWithTypeName, afterValueAction, duplicatePropertyNamesChecker, epmValueCache, epmSourcePathSegment, projectedProperties); this.DecreaseRecursionDepth(); return propertyWritten; }
/// <summary>Writes all the properties of the specified resource or complex object.</summary> /// <param name="expanded">Expanded properties for the result.</param> /// <param name="customObject">Resource or complex object with properties to write out.</param> /// <param name="resourceType">resourceType containing metadata about the current custom object</param> /// <param name="absoluteUri">absolute uri for the given resource</param> /// <param name="relativeUri">relative uri for the given resource</param> /// <param name="item">Item in which to place links / expansions.</param> /// <param name="content">Content in which to place values.</param> /// <param name="currentSourceRoot">Epm source sub-tree corresponding to <paramref name="customObject"/></param> private void WriteObjectProperties(IExpandedResult expanded, object customObject, ResourceType resourceType, Uri absoluteUri, string relativeUri, SyndicationItem item, DictionaryContent content, EpmSourcePathSegment currentSourceRoot) { Debug.Assert(customObject != null, "customObject != null"); Debug.Assert(resourceType != null, "resourceType != null"); Debug.Assert(!String.IsNullOrEmpty(relativeUri), "!String.IsNullOrEmpty(relativeUri)"); if (absoluteUri == null && resourceType.ResourceTypeKind == ResourceTypeKind.EntityType) { // entity type should have an URI, complex type should not have an URI // If the static type of the object is "Object", we will mistreat an entity type as complex type and hit this situation throw new DataServiceException(500, Strings.BadProvider_InconsistentEntityOrComplexTypeUsage(resourceType.Name)); } this.RecurseEnter(); try { List<ResourcePropertyInfo> navProperties = null; IEnumerable<ProjectionNode> projectionNodes = null; if (resourceType.ResourceTypeKind == ResourceTypeKind.EntityType) { Debug.Assert(this.CurrentContainer != null, "this.CurrentContainer != null"); if (this.Provider.IsEntityTypeDisallowedForSet(this.CurrentContainer, resourceType)) { throw new InvalidOperationException(Strings.BaseServiceProvider_NavigationPropertiesOnDerivedEntityTypesNotSupported(resourceType.FullName, this.CurrentContainer.Name)); } navProperties = new List<ResourcePropertyInfo>(resourceType.Properties.Count); projectionNodes = this.GetProjections(); } if (projectionNodes == null) { var action = resourceType.DictionarySerializerDelegate; if (action == null && this.Provider.IsV1Provider) { Module module = typeof(SyndicationSerializer).Module; Type customObjectType = customObject.GetType(); Type[] parameterTypes = new Type[] { typeof(object), typeof(DictionaryContent) }; DynamicMethod method = new DynamicMethod("content_populator", typeof(void), parameterTypes, module, false /* skipVisibility */); ILGenerator generator = method.GetILGenerator(); MethodInfo methodWritePrimitiveValue = typeof(SyndicationSerializer).GetMethod("WritePrimitiveValue", BindingFlags.Static | BindingFlags.NonPublic); // Downcast the argument. generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Castclass, customObjectType); foreach (ResourceProperty property in resourceType.Properties.Where(p => p.TypeKind == ResourceTypeKind.Primitive)) { if (SyndicationSerializer.EpmNeedToSkip(currentSourceRoot, property.Name)) { continue; } // WritePrimitiveValue(propertyValue, property.Name, property.ResourceType, content); generator.Emit(OpCodes.Dup); generator.Emit(OpCodes.Call, resourceType.GetPropertyInfo(property).GetGetMethod()); if (property.Type.IsValueType) { generator.Emit(OpCodes.Box, property.Type); } generator.Emit(OpCodes.Ldstr, property.Name); generator.Emit(OpCodes.Ldstr, property.ResourceType.FullName); generator.Emit(OpCodes.Ldarg_1); generator.Emit(OpCodes.Call, methodWritePrimitiveValue); } generator.Emit(OpCodes.Pop); generator.Emit(OpCodes.Ret); action = (Action<object, DictionaryContent>)method.CreateDelegate(typeof(Action<object, DictionaryContent>), null); resourceType.DictionarySerializerDelegate = action; } if (action != null) { action(customObject, content); } else { foreach (ResourceProperty property in resourceType.Properties.Where(p => p.TypeKind == ResourceTypeKind.Primitive)) { object propertyValue = WebUtil.GetPropertyValue(this.Provider, customObject, resourceType, property, null); if (SyndicationSerializer.EpmNeedToSkip(currentSourceRoot, property.Name)) { continue; } WritePrimitiveValue(propertyValue, property.Name, property.ResourceType.FullName, content); } } foreach (ResourceProperty property in this.Provider.GetResourceProperties(this.CurrentContainer, resourceType)) { string propertyName = property.Name; if (property.TypeKind == ResourceTypeKind.EntityType) { Debug.Assert(navProperties != null, "navProperties list must be assigned for entity types"); object propertyValue = (this.ShouldExpandSegment(property.Name)) ? GetExpandedProperty(this.Provider, expanded, customObject, property) : null; navProperties.Add(new ResourcePropertyInfo(property, propertyValue)); } else { if (property.TypeKind == ResourceTypeKind.ComplexType) { object propertyValue = WebUtil.GetPropertyValue(this.Provider, customObject, resourceType, property, null); bool needPop = this.PushSegmentForProperty(property); this.WriteComplexObjectValue( propertyValue, propertyName, property.ResourceType, relativeUri + "/" + property.Name, content, SyndicationSerializer.EpmGetComplexPropertySegment(currentSourceRoot, property.Name)); this.PopSegmentName(needPop); } } } if (resourceType.IsOpenType) { IEnumerable<KeyValuePair<string, object>> properties = this.Provider.GetOpenPropertyValues(customObject); foreach (KeyValuePair<string, object> property in properties) { string propertyName = property.Key; if (String.IsNullOrEmpty(propertyName)) { throw new DataServiceException(500, Strings.Syndication_InvalidOpenPropertyName(resourceType.FullName)); } Type valueType; ResourceType propertyResourceType; object value = property.Value; if (value == null || value == DBNull.Value) { valueType = typeof(string); propertyResourceType = ResourceType.PrimitiveStringResourceType; } else { valueType = value.GetType(); propertyResourceType = WebUtil.GetResourceType(this.Provider, value); } // A null ResourceType indicates a ----ed type (eg, IntPtr or DateTimeOffset). So ignore it. if (propertyResourceType == null) { throw new DataServiceException(500, Strings.Syndication_InvalidOpenPropertyType(propertyName)); } if (propertyResourceType.ResourceTypeKind == ResourceTypeKind.Primitive) { if (value != null && SyndicationSerializer.EpmNeedToSkip(currentSourceRoot, propertyName)) { continue; } WritePrimitiveValue(value, propertyName, propertyResourceType.FullName, content); } else { if (propertyResourceType.ResourceTypeKind == ResourceTypeKind.ComplexType) { Debug.Assert(propertyResourceType.InstanceType == valueType, "propertyResourceType.Type == valueType"); this.WriteComplexObjectValue( value, propertyName, propertyResourceType, relativeUri + "/" + propertyName, content, SyndicationSerializer.EpmGetComplexPropertySegment(currentSourceRoot, propertyName)); } else { Debug.Assert( propertyResourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "propertyResourceType.ResourceTypeKind == ResourceTypeKind.EntityType -- otherwise should have been processed as primitve or complex type."); // Open navigation properties are not supported on OpenTypes throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(propertyName)); } } } } } else { foreach (ProjectionNode projectionNode in projectionNodes) { string propertyName = projectionNode.PropertyName; ResourceProperty property = resourceType.TryResolvePropertyName(propertyName); // First solve the normal entity type property - turn it into a nav. property record if (property != null && property.TypeKind == ResourceTypeKind.EntityType) { Debug.Assert(navProperties != null, "navProperties list must be assigned for entity types"); // By calling the GetResourceProperties we will use the cached list of properties // for the given type and set. But we have to search through it. // We could use the GetContainer (since that's what the GetResourceProperties does) and check // if it returns null, but result of that is only partially cached so it might be expensive // to evaluate for each item in the feed. if (this.Provider.GetResourceProperties(this.CurrentContainer, resourceType).Contains(property)) { object expandedPropertyValue = (this.ShouldExpandSegment(propertyName)) ? GetExpandedProperty(this.Provider, expanded, customObject, property) : null; navProperties.Add(new ResourcePropertyInfo(property, expandedPropertyValue)); } continue; } // Now get the property value object propertyValue = WebUtil.GetPropertyValue(this.Provider, customObject, resourceType, property, property == null ? propertyName : null); // Determine the type of the property ResourceType propertyResourceType; if (property != null) { propertyResourceType = property.ResourceType; } else { if (propertyValue == null || propertyValue == DBNull.Value) { propertyResourceType = ResourceType.PrimitiveStringResourceType; } else { propertyResourceType = WebUtil.GetResourceType(this.Provider, propertyValue); // A null ResourceType indicates a ----ed type (eg, IntPtr or DateTimeOffset). So ignore it. if (propertyResourceType == null) { throw new DataServiceException(500, Strings.Syndication_InvalidOpenPropertyType(propertyName)); } } } // And write out the value (depending on the type of the property) if (propertyResourceType.ResourceTypeKind == ResourceTypeKind.Primitive) { if (propertyValue == DBNull.Value) { propertyValue = null; } if (propertyValue != null && SyndicationSerializer.EpmNeedToSkip(currentSourceRoot, propertyName)) { continue; } WritePrimitiveValue(propertyValue, propertyName, propertyResourceType.FullName, content); } else if (propertyResourceType.ResourceTypeKind == ResourceTypeKind.ComplexType) { bool needPop = false; if (property != null) { needPop = this.PushSegmentForProperty(property); } this.WriteComplexObjectValue( propertyValue, propertyName, propertyResourceType, relativeUri + "/" + propertyName, content, SyndicationSerializer.EpmGetComplexPropertySegment(currentSourceRoot, propertyName)); if (property != null) { this.PopSegmentName(needPop); } } else { Debug.Assert( propertyResourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "propertyResourceType.ResourceTypeKind == ResourceTypeKind.EntityType -- otherwise should have been processed as primitve or complex type."); // Open navigation properties are not supported on OpenTypes throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(propertyName)); } } } if (resourceType.ResourceTypeKind == ResourceTypeKind.EntityType) { for (int i = 0; i < navProperties.Count; i++) { ResourcePropertyInfo propertyInfo = navProperties[i]; ResourceProperty navProperty = propertyInfo.Property; Debug.Assert( navProperty.IsOfKind(ResourcePropertyKind.ResourceReference) || navProperty.IsOfKind(ResourcePropertyKind.ResourceSetReference), "this must be nav property"); // Generate a link - see http://tools.ietf.org/html/rfc4287#section-4.2.7 string linkType = navProperty.IsOfKind(ResourcePropertyKind.ResourceReference) ? XmlConstants.AtomEntryElementName : XmlConstants.AtomFeedElementName; linkType = String.Format(CultureInfo.InvariantCulture, "{0};{1}={2}", XmlConstants.MimeApplicationAtom, XmlConstants.AtomTypeAttributeName, linkType); string segmentIdentifier = navProperty.Name; if (!this.ShouldExpandSegment(navProperty.Name)) { WriteDeferredContentElement( XmlConstants.DataWebRelatedNamespace + navProperty.Name, navProperty.Name, relativeUri + "/" + segmentIdentifier, linkType, item); } else { object propertyValue = propertyInfo.Value; IExpandedResult expandedResultPropertyValue = propertyValue as IExpandedResult; object expandedPropertyValue = expandedResultPropertyValue != null ? GetExpandedElement(expandedResultPropertyValue) : propertyValue; string propertyRelativeUri = relativeUri + "/" + segmentIdentifier; Uri propertyAbsoluteUri = RequestUriProcessor.AppendUnescapedSegment(absoluteUri, segmentIdentifier); SyndicationLink link = new SyndicationLink(); link.RelationshipType = XmlConstants.DataWebRelatedNamespace + navProperty.Name; link.Title = navProperty.Name; link.Uri = new Uri(propertyRelativeUri, UriKind.RelativeOrAbsolute); link.MediaType = linkType; item.Links.Add(link); bool needPop = this.PushSegmentForProperty(navProperty); // if this.CurrentContainer is null, the target set of the navigation property is hidden. if (this.CurrentContainer != null) { if (navProperty.IsOfKind(ResourcePropertyKind.ResourceSetReference)) { IEnumerable enumerable; bool collection = WebUtil.IsElementIEnumerable(expandedPropertyValue, out enumerable); Debug.Assert(collection, "metadata loading must have ensured that navigation set properties must implement IEnumerable"); SyndicationFeed feed = new SyndicationFeed(); InlineAtomFeed inlineFeedExtension = new InlineAtomFeed(feed, this.factory); link.ElementExtensions.Add(inlineFeedExtension); IEnumerator enumerator = enumerable.GetEnumerator(); try { bool hasMoved = enumerator.MoveNext(); this.WriteFeedElements( propertyValue as IExpandedResult, enumerator, navProperty.ResourceType, navProperty.Name, propertyAbsoluteUri, propertyRelativeUri, hasMoved, feed, true); } catch { WebUtil.Dispose(enumerator); throw; } } else { SyndicationItem inlineItem = new SyndicationItem(); this.WriteEntryElement(propertyValue as IExpandedResult, expandedPropertyValue, navProperty.ResourceType, propertyAbsoluteUri, propertyRelativeUri, inlineItem); InlineAtomItem inlineItemExtension = new InlineAtomItem(inlineItem, this.factory); link.ElementExtensions.Add(inlineItemExtension); } } this.PopSegmentName(needPop); } } } } finally { // The matching call to RecurseLeave is in a try/finally block not because it's necessary in the // presence of an exception (progress will halt anyway), but because it's easier to maintain in the // code in the presence of multiple exit points (returns). this.RecurseLeave(); } }
/// <summary> /// Reads a property value starting on a complex value. /// </summary> /// <param name="complexValue">The complex value to start with.</param> /// <param name="complexPropertySegment">The EPM source path segment which points to the <paramref name="complexValue"/>.</param> /// <param name="epmValueCache">The EPM value cache to use.</param> /// <param name="sourceSegmentIndex">The index in the property value path to start with.</param> /// <param name="resourceType">The resource type of the complex value.</param> /// <param name="metadata">The metadata provider to use.</param> /// <param name="nullOnParentProperty">true if the value of the property is null because one of its parent properties was null, in this case /// the return value of the method is always null. false if the value of the property is the actual property value which may or may not be null.</param> /// <returns>The value of the property (may be null), or null if the property itself was not found due to one of its parent properties being null.</returns> private object ReadComplexPropertyValue( ODataComplexValue complexValue, EpmSourcePathSegment complexPropertySegment, EpmValueCache epmValueCache, int sourceSegmentIndex, ResourceType resourceType, DataServiceMetadataProviderWrapper metadata, out bool nullOnParentProperty) { Debug.Assert(this.propertyValuePath != null, "The propertyValuePath should have been initialized by now."); Debug.Assert(this.propertyValuePath.Length > sourceSegmentIndex, "The propertyValuePath must be at least as long as the source segment index."); Debug.Assert(epmValueCache != null, "epmValueCache != null"); Debug.Assert(sourceSegmentIndex >= 0, "sourceSegmentIndex >= 0"); Debug.Assert(resourceType != null, "resourceType != null"); if (complexValue == null) { nullOnParentProperty = true; return null; } return this.ReadPropertyValue( EpmValueCache.GetComplexValueProperties(epmValueCache, complexPropertySegment, complexValue, false), sourceSegmentIndex, resourceType, metadata, epmValueCache, out nullOnParentProperty); }
/// <summary> /// Reads a property value starting 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)); }
internal bool WriteComplexValue( ODataComplexValue complexValue, IEdmTypeReference metadataTypeReference, bool isOpenPropertyType, bool isWritingCollection, Action beforeValueAction, Action afterValueAction, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, CollectionWithoutExpectedTypeValidator collectionValidator, EpmValueCache epmValueCache, EpmSourcePathSegment epmSourcePathSegment, ProjectedPropertiesAnnotation projectedProperties) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(complexValue != null, "complexValue != null"); string typeName = complexValue.TypeName; if (collectionValidator != null) { collectionValidator.ValidateCollectionItem(typeName, EdmTypeKind.Complex); } this.IncreaseRecursionDepth(); // resolve the type name to the type; if no type name is specified we will use the // type inferred from metadata IEdmComplexTypeReference complexTypeReference = TypeNameOracle.ResolveAndValidateTypeNameForValue(this.Model, metadataTypeReference, complexValue, isOpenPropertyType).AsComplexOrNull(); string collectionItemTypeName; typeName = this.AtomOutputContext.TypeNameOracle.GetValueTypeNameForWriting(complexValue, complexTypeReference, complexValue.GetAnnotation <SerializationTypeNameAnnotation>(), collectionValidator, out collectionItemTypeName); Debug.Assert(collectionItemTypeName == null, "collectionItemTypeName == null"); Action beforeValueCallbackWithTypeName = beforeValueAction; if (typeName != null) { // The beforeValueAction (if specified) will write the actual property element start. // So if we are to write the type attribute, we must postpone that after the start element was written. // And so we chain the existing action with our type attribute writing and use that // as the before action instead. if (beforeValueAction != null) { beforeValueCallbackWithTypeName = () => { beforeValueAction(); this.WritePropertyTypeAttribute(typeName); }; } else { this.WritePropertyTypeAttribute(typeName); } } // NOTE: see the comments on ODataWriterBehavior.UseV1ProviderBehavior for more information // NOTE: We have to check for ProjectedPropertiesAnnotation.Empty here to avoid filling the cache for // complex values we are writing only to ensure we don't have nested EPM-mapped null values // that will end up in the content eventually. if (this.MessageWriterSettings.WriterBehavior != null && this.MessageWriterSettings.WriterBehavior.UseV1ProviderBehavior && !object.ReferenceEquals(projectedProperties, ProjectedPropertiesAnnotation.EmptyProjectedPropertiesInstance)) { IEdmComplexType complexType = (IEdmComplexType)complexTypeReference.Definition; CachedPrimitiveKeepInContentAnnotation keepInContentCache = this.Model.EpmCachedKeepPrimitiveInContent(complexType); if (keepInContentCache == null) { // we are about to write the first value of the given type; compute the keep-in-content information for the primitive properties of this type. List <string> keepInContentPrimitiveProperties = null; // initialize the cache with all primitive properties foreach (IEdmProperty edmProperty in complexType.Properties().Where(p => p.Type.IsODataPrimitiveTypeKind())) { // figure out the keep-in-content value EntityPropertyMappingAttribute entityPropertyMapping = EpmWriterUtils.GetEntityPropertyMapping(epmSourcePathSegment, edmProperty.Name); if (entityPropertyMapping != null && entityPropertyMapping.KeepInContent) { if (keepInContentPrimitiveProperties == null) { keepInContentPrimitiveProperties = new List <string>(); } keepInContentPrimitiveProperties.Add(edmProperty.Name); } } this.Model.SetAnnotationValue <CachedPrimitiveKeepInContentAnnotation>(complexType, new CachedPrimitiveKeepInContentAnnotation(keepInContentPrimitiveProperties)); } } bool propertyWritten = this.WriteProperties( complexTypeReference == null ? null : complexTypeReference.ComplexDefinition(), EpmValueCache.GetComplexValueProperties(epmValueCache, complexValue, true), isWritingCollection, beforeValueCallbackWithTypeName, afterValueAction, duplicatePropertyNamesChecker, epmValueCache, epmSourcePathSegment, projectedProperties); this.DecreaseRecursionDepth(); return(propertyWritten); }
/// <summary> /// Sets path to the source property. /// </summary> /// <param name="path">The path as an array of source path segments.</param> internal void SetPropertyValuePath(EpmSourcePathSegment[] path) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(path != null, "path != null"); Debug.Assert(path.Length > 0, "The path must contain at least one segment."); Debug.Assert(this.propertyValuePath == null, "The property value path was already initialized."); this.propertyValuePath = path; }
/// <summary> /// Checks if a particular property value should be skipped from the content section due to /// EntityProperty mappings for friendly feeds /// </summary> /// <param name="currentSourceRoot">Current root segment in the source tree for a resource type</param> /// <param name="propertyName">Name of the property being checked for</param> /// <returns>true if skipping of property value is needed, false otherwise</returns> private static bool EpmNeedToSkip(EpmSourcePathSegment currentSourceRoot, String propertyName) { if (currentSourceRoot != null) { EpmSourcePathSegment epmProperty = currentSourceRoot.SubProperties.Find(subProp => subProp.PropertyName == propertyName); if (epmProperty != null) { Debug.Assert(epmProperty.SubProperties.Count == 0, "Complex type added as leaf node in EPM tree."); Debug.Assert(epmProperty.EpmInfo != null, "Found a non-leaf property for which EpmInfo is not set."); Debug.Assert(epmProperty.EpmInfo.Attribute != null, "Attribute should always be initialized for EpmInfo."); if (epmProperty.EpmInfo.Attribute.KeepInContent == false) { return true; } } } return false; }
/// <summary> /// Obtains the child EPM segment corresponding to the given <paramref name="propertyName"/> /// </summary> /// <param name="currentSourceRoot">Current root segment</param> /// <param name="propertyName">Name of property</param> /// <returns>Child segment or null if there is not segment corresponding to the given <paramref name="propertyName"/></returns> private static EpmSourcePathSegment EpmGetComplexPropertySegment(EpmSourcePathSegment currentSourceRoot, String propertyName) { if (currentSourceRoot != null) { return currentSourceRoot.SubProperties.Find(subProp => subProp.PropertyName == propertyName); } else { return null; } }
/// <summary> /// Write the content of the given entry. /// </summary> /// <param name="entry">The entry for which to write properties.</param> /// <param name="entryType">The <see cref="IEdmEntityType"/> of the entry (or null if not metadata is available).</param> /// <param name="propertiesValueCache">The cache of properties.</param> /// <param name="rootSourcePathSegment">The root of the EPM source tree, if there's an EPM applied.</param> /// <param name="projectedProperties">Set of projected properties, or null if all properties should be written.</param> private void WriteEntryContent( ODataEntry entry, IEdmEntityType entryType, EntryPropertiesValueCache propertiesValueCache, EpmSourcePathSegment rootSourcePathSegment, ProjectedPropertiesAnnotation projectedProperties) { Debug.Assert(entry != null, "entry != null"); Debug.Assert(propertiesValueCache != null, "propertiesValueCache != null"); ODataStreamReferenceValue mediaResource = entry.MediaResource; if (mediaResource == null) { // <content type="application/xml"> this.atomOutputContext.XmlWriter.WriteStartElement( AtomConstants.AtomNamespacePrefix, AtomConstants.AtomContentElementName, AtomConstants.AtomNamespace); this.atomOutputContext.XmlWriter.WriteAttributeString( AtomConstants.AtomTypeAttributeName, MimeConstants.MimeApplicationXml); this.atomEntryAndFeedSerializer.AssertRecursionDepthIsZero(); this.atomEntryAndFeedSerializer.WriteProperties( entryType, propertiesValueCache.EntryProperties, false /* isWritingCollection */, this.atomEntryAndFeedSerializer.WriteEntryPropertiesStart, this.atomEntryAndFeedSerializer.WriteEntryPropertiesEnd, this.DuplicatePropertyNamesChecker, propertiesValueCache, rootSourcePathSegment, projectedProperties); this.atomEntryAndFeedSerializer.AssertRecursionDepthIsZero(); // </content> this.atomOutputContext.XmlWriter.WriteEndElement(); } else { WriterValidationUtils.ValidateStreamReferenceValue(mediaResource, true); this.atomEntryAndFeedSerializer.WriteEntryMediaEditLink(mediaResource); if (mediaResource.ReadLink != null) { // <content type="type" src="src"> this.atomOutputContext.XmlWriter.WriteStartElement( AtomConstants.AtomNamespacePrefix, AtomConstants.AtomContentElementName, AtomConstants.AtomNamespace); Debug.Assert(!string.IsNullOrEmpty(mediaResource.ContentType), "The default stream content type should have been validated by now. If we have a read link we must have a non-empty content type as well."); this.atomOutputContext.XmlWriter.WriteAttributeString( AtomConstants.AtomTypeAttributeName, mediaResource.ContentType); this.atomOutputContext.XmlWriter.WriteAttributeString( AtomConstants.MediaLinkEntryContentSourceAttributeName, this.atomEntryAndFeedSerializer.UriToUrlAttributeValue(mediaResource.ReadLink)); // </content> this.atomOutputContext.XmlWriter.WriteEndElement(); } this.atomEntryAndFeedSerializer.AssertRecursionDepthIsZero(); this.atomEntryAndFeedSerializer.WriteProperties( entryType, propertiesValueCache.EntryProperties, false /* isWritingCollection */, this.atomEntryAndFeedSerializer.WriteEntryPropertiesStart, this.atomEntryAndFeedSerializer.WriteEntryPropertiesEnd, this.DuplicatePropertyNamesChecker, propertiesValueCache, rootSourcePathSegment, projectedProperties); this.atomEntryAndFeedSerializer.AssertRecursionDepthIsZero(); } }
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)); }