/// <summary>Compares the defining type of this info and other EpmInfo object.</summary> /// <param name="other">The other EpmInfo object to compare to.</param> /// <returns>true if the defining types are the same</returns> internal bool DefiningTypesAreEqual(EntityPropertyMappingInfo other) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(other != null, "other != null"); return(this.DefiningType == other.DefiningType); }
/// <summary> /// Validates conditional mapping. /// </summary> /// <param name="epmInfo">Epm mapping info</param> private static void ValidateConditionalMapping(EntityPropertyMappingInfo epmInfo) { Debug.Assert(epmInfo.CriteriaValue != null, "epmInfo.CriteriaValue != null"); String criteriaValue = epmInfo.CriteriaValue; if (epmInfo.Criteria == EpmSyndicationCriteria.LinkRel && !EntityPropertyMappingInfo.IsValidLinkRelCriteriaValue(criteriaValue)) { throw new ODataException(Strings.EpmSourceTree_ConditionalMappingInvalidLinkRelCriteriaValue( criteriaValue, epmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name)); } if (epmInfo.Criteria == EpmSyndicationCriteria.CategoryScheme && !EntityPropertyMappingInfo.IsValidCategorySchemeCriteriaValue(criteriaValue)) { throw new ODataException(Strings.EpmSourceTree_ConditionalMappingInvalidCategorySchemeCriteriaValue( criteriaValue, epmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name)); } if (epmInfo.IsAtomLinkMapping) { if (epmInfo.Attribute.TargetSyndicationItem == SyndicationItemProperty.LinkRel) { throw new ODataException(Strings.EpmTargetTree_ConditionalMappingToCriteriaAttribute( epmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name, epmInfo.Attribute.TargetPath)); } } else if (epmInfo.IsAtomCategoryMapping) { if (epmInfo.Attribute.TargetSyndicationItem == SyndicationItemProperty.CategoryScheme) { throw new ODataException(Strings.EpmTargetTree_ConditionalMappingToCriteriaAttribute( epmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name, epmInfo.Attribute.TargetPath)); } } else { throw new ODataException(Strings.EpmTargetTree_ConditionalMappingToNonConditionalSyndicationItem( epmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name, epmInfo.Attribute.TargetPath)); } }
/// <summary> /// Trims the start of the property value path by removing the path to the multivalue property. /// </summary> /// <param name="multiValueEpmInfo">Epm info for the multivalue property itself..</param> internal void TrimMultiValueItemPropertyPath(EntityPropertyMappingInfo multiValueEpmInfo) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(multiValueEpmInfo != null, "multiValueEpmInfo != null"); Debug.Assert(multiValueEpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty, "The multiValueEpmInfo must be a multiValue property info."); #if DEBUG Debug.Assert(multiValueEpmInfo.propertyValuePath.Length <= this.propertyValuePath.Length, "The prefix is longer than the actual source path."); for (int i = 0; i < multiValueEpmInfo.propertyValuePath.Length; i++) { Debug.Assert(multiValueEpmInfo.propertyValuePath[i] == this.propertyValuePath[i], "The prefix doesn't match the actual source path."); } #endif this.propertyValuePath = this.propertyValuePath.Skip(multiValueEpmInfo.propertyValuePath.Length).ToArray(); }
/// <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> /// Given an <see cref="EntityPropertyMappingInfo"/> gives the correct target path for it /// </summary> /// <param name="epmInfo">Given <see cref="EntityPropertyMappingInfo"/></param> /// <returns>String with the correct value for the target path</returns> private static String GetPropertyNameFromEpmInfo(EntityPropertyMappingInfo epmInfo) { if (epmInfo.Attribute.TargetSyndicationItem == SyndicationItemProperty.CustomProperty) { return(epmInfo.Attribute.TargetPath); } else { // for EF provider we want to return a name that corresponds to attribute in the edmx file while for CLR provider // and the client we want to return a name that corresponds to the enum value used in EntityPropertyMapping attribute. return (#if ASTORIA_SERVER epmInfo.IsEFProvider ? EpmTranslate.MapSyndicationPropertyToEpmTargetPath(epmInfo.Attribute.TargetSyndicationItem) : #endif epmInfo.Attribute.TargetSyndicationItem.ToString()); } }
internal void Add(EntityPropertyMappingInfo epmInfo) { 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)); } } } }
/// <summary> /// Writes the syndication part of EPM for an entry into ATOM metadata OM. /// </summary> /// <param name="epmTargetTree">The EPM target tree to use.</param> /// <param name="epmValueCache">The entry properties value cache to use to access the properties.</param> /// <param name="resourceType">The resource type of the entry.</param> /// <param name="metadata">The metadata provider to use.</param> /// <param name="version">The version of OData protocol to use.</param> /// <returns>The ATOM metadata OM with the EPM values populated.</returns> internal static AtomEntryMetadata WriteEntryEpm( EpmTargetTree epmTargetTree, EntryPropertiesValueCache epmValueCache, ResourceType resourceType, DataServiceMetadataProviderWrapper metadata, ODataVersion version) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(epmTargetTree != null, "epmTargetTree != null"); Debug.Assert(epmValueCache != null, "epmValueCache != null"); Debug.Assert(resourceType != null, "For any EPM to exist the metadata must be available."); // If there are no syndication mappings, just return null. EpmTargetPathSegment syndicationRootSegment = epmTargetTree.SyndicationRoot; Debug.Assert(syndicationRootSegment != null, "EPM Target tree must always have syndication root."); if (syndicationRootSegment.SubSegments.Count == 0) { return(null); } AtomEntryMetadata entryMetadata = new AtomEntryMetadata(); foreach (EpmTargetPathSegment targetSegment in syndicationRootSegment.SubSegments) { if (targetSegment.IsMultiValueProperty) { Debug.Assert( targetSegment.EpmInfo != null && targetSegment.EpmInfo.Attribute != null, "MultiValue property target segment must have EpmInfo and the Epm Attribute."); ODataVersionChecker.CheckMultiValueProperties(version, targetSegment.EpmInfo.Attribute.SourcePath); // WriteMultiValueEpm(entry, targetSegment, epmValueCache); throw new NotImplementedException(); } else if (targetSegment.HasContent) { EntityPropertyMappingInfo epmInfo = targetSegment.EpmInfo; Debug.Assert( epmInfo != null && epmInfo.Attribute != null, "If the segment has content it must have EpmInfo which in turn must have the Epm attribute"); bool nullOnParentProperty; object propertyValue = epmInfo.ReadEntryPropertyValue(epmValueCache, resourceType, metadata, out nullOnParentProperty); string textPropertyValue = EpmWriterUtils.GetPropertyValueAsText(propertyValue); switch (epmInfo.Attribute.TargetSyndicationItem) { case SyndicationItemProperty.Updated: entryMetadata.Updated = CreateDateTimeValue(propertyValue, SyndicationItemProperty.Updated, version); break; case SyndicationItemProperty.Published: entryMetadata.Published = CreateDateTimeValue(propertyValue, SyndicationItemProperty.Published, version); break; case SyndicationItemProperty.Rights: entryMetadata.Rights = CreateAtomTextConstruct(textPropertyValue, epmInfo.Attribute.TargetTextContentKind, version); break; case SyndicationItemProperty.Summary: entryMetadata.Summary = CreateAtomTextConstruct(textPropertyValue, epmInfo.Attribute.TargetTextContentKind, version); break; case SyndicationItemProperty.Title: entryMetadata.Title = CreateAtomTextConstruct(textPropertyValue, epmInfo.Attribute.TargetTextContentKind, version); break; default: throw new ODataException(Strings.General_InternalError(InternalErrorCodes.EpmSyndicationWriter_WriteEntryEpm_ContentTarget)); } } else if (targetSegment.SegmentName == AtomConstants.AtomAuthorElementName) { AtomPersonMetadata authorMetadata = WritePersonEpm(targetSegment, epmValueCache, resourceType, metadata); Debug.Assert(entryMetadata.Authors == null, "Found two mappings to author, that is invalid."); if (authorMetadata != null) { entryMetadata.Authors = CreateSinglePersonList(authorMetadata); } } else if (targetSegment.SegmentName == AtomConstants.AtomContributorElementName) { AtomPersonMetadata contributorMetadata = WritePersonEpm(targetSegment, epmValueCache, resourceType, metadata); Debug.Assert(entryMetadata.Contributors == null, "Found two mappings to contributor, that is invalid."); if (contributorMetadata != null) { entryMetadata.Contributors = CreateSinglePersonList(contributorMetadata); } } else if (targetSegment.SegmentName == AtomConstants.AtomLinkElementName) { AtomLinkMetadata linkMetadata = new AtomLinkMetadata(); //// WriteLinkEpm(entry, targetSegment, epmValueCache); Debug.Assert(targetSegment.CriteriaValue != null, "Mapping to link must be conditional."); linkMetadata.Relation = targetSegment.CriteriaValue; List <AtomLinkMetadata> links; if (entryMetadata.Links == null) { links = new List <AtomLinkMetadata>(); entryMetadata.Links = links; } else { links = entryMetadata.Links as List <AtomLinkMetadata>; Debug.Assert(links != null, "AtomEntryMetadata.Links must be of type List<AtomLinkMetadata> since we create it like that."); } links.Add(linkMetadata); throw new NotImplementedException(); } else if (targetSegment.SegmentName == AtomConstants.AtomCategoryElementName) { AtomCategoryMetadata categoryMetadata = new AtomCategoryMetadata(); //// WriteCategoryEpm(entry, targetSegment, epmValueCache) Debug.Assert(targetSegment.CriteriaValue != null, "Mapping to category must be conditional."); categoryMetadata.Scheme = targetSegment.CriteriaValue; List <AtomCategoryMetadata> categories; if (entryMetadata.Categories == null) { categories = new List <AtomCategoryMetadata>(); entryMetadata.Categories = categories; } else { categories = entryMetadata.Links as List <AtomCategoryMetadata>; Debug.Assert(categories != null, "AtomEntryMetadata.Categories must be of type List<AtomCategoryMetadata> since we create it like that."); } categories.Add(categoryMetadata); throw new NotImplementedException(); } else { throw new ODataException(Strings.General_InternalError(InternalErrorCodes.EpmSyndicationWriter_WriteEntryEpm_TargetSegment)); } } return(entryMetadata); }
/// <summary> /// Removes a path in the tree which is obtained by looking at the EntityPropertyMappingAttribute in the <paramref name="epmInfo"/>. /// </summary> /// <param name="epmInfo">EnitityPropertyMappingInfo holding the target path</param> internal void Remove(EntityPropertyMappingInfo epmInfo) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(epmInfo != null, "epmInfo != null"); // We should never try to remove a multiValue item property from the target tree. // If derived type redefines mapping for a given multiValue, the multiValue node itself should be removed first and then all its new items // should be added (but since the multiValue node, that is their parent, was replaces, there should be no collisions and thus no need to remove anything) Debug.Assert(epmInfo.MultiValueStatus != EntityPropertyMappingMultiValueStatus.MultiValueItemProperty, "We should never try to remove a multiValue item property."); String targetName = epmInfo.Attribute.TargetPath; String namespaceUri = epmInfo.Attribute.TargetNamespaceUri; EpmTargetPathSegment currentSegment = epmInfo.IsSyndicationMapping ? this.SyndicationRoot : this.NonSyndicationRoot; List <EpmTargetPathSegment> activeSubSegments = currentSegment.SubSegments; Debug.Assert(!String.IsNullOrEmpty(targetName), "Must have been validated during EntityPropertyMappingAttribute construction"); String[] targetSegments = targetName.Split('/'); for (int i = 0; i < targetSegments.Length; i++) { String targetSegment = targetSegments[i]; Debug.Assert(targetSegment.Length > 0 && (targetSegment[0] != '@' || i == targetSegments.Length - 1), "Target segments should have been checked when adding the path to the tree"); EpmTargetPathSegment foundSegment = activeSubSegments.FirstOrDefault( segment => segment.SegmentName == targetSegment && (epmInfo.IsSyndicationMapping || segment.SegmentNamespaceUri == namespaceUri) && segment.MatchCriteria(epmInfo.CriteriaValue, epmInfo.Criteria)); if (foundSegment != null) { currentSegment = foundSegment; } else { return; } activeSubSegments = currentSegment.SubSegments; } // Recursively remove all the parent segments which will have no more children left // after removal of the current segment node if (currentSegment.EpmInfo != null) { // Since we are removing a property with KeepInContent false, we should decrement the count if (!currentSegment.EpmInfo.Attribute.KeepInContent) { if (currentSegment.EpmInfo.IsAtomLinkMapping || currentSegment.EpmInfo.IsAtomCategoryMapping) { this.countOfNonContentV3mappings--; } else { this.countOfNonContentV2mappings--; } } EpmTargetPathSegment parentSegment = null; do { // We should never be removing the multiValue property due to its children being removed Debug.Assert( currentSegment.EpmInfo == null || currentSegment.EpmInfo.MultiValueStatus != EntityPropertyMappingMultiValueStatus.MultiValueProperty || parentSegment == null, "We should never be removing the multiValue property due to its child being removed. The source tree Add method should remove the multiValue property node itself first in that case."); parentSegment = currentSegment.ParentSegment; parentSegment.SubSegments.Remove(currentSegment); currentSegment = parentSegment; }while (currentSegment.ParentSegment != null && !currentSegment.HasContent && currentSegment.SubSegments.Count == 0); } }
internal void Add(EntityPropertyMappingInfo epmInfo) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(epmInfo != null, "epmInfo != null"); String targetPath = epmInfo.Attribute.TargetPath; String namespaceUri = epmInfo.Attribute.TargetNamespaceUri; String namespacePrefix = epmInfo.Attribute.TargetNamespacePrefix; EpmTargetPathSegment currentSegment = epmInfo.IsSyndicationMapping ? this.SyndicationRoot : this.NonSyndicationRoot; IList <EpmTargetPathSegment> activeSubSegments = currentSegment.SubSegments; Debug.Assert(!String.IsNullOrEmpty(targetPath), "Must have been validated during EntityPropertyMappingAttribute construction"); String[] targetSegments = targetPath.Split('/'); EpmTargetPathSegment foundSegment = null; EpmTargetPathSegment multiValueSegment = null; for (int i = 0; i < targetSegments.Length; i++) { String targetSegment = targetSegments[i]; if (targetSegment.Length == 0) { throw new ODataException(Strings.EpmTargetTree_InvalidTargetPath(targetPath)); } if (targetSegment[0] == '@' && i != targetSegments.Length - 1) { throw new ODataException(Strings.EpmTargetTree_AttributeInMiddle(targetSegment)); } foundSegment = activeSubSegments.SingleOrDefault( segment => segment.SegmentName == targetSegment && (epmInfo.IsSyndicationMapping || segment.SegmentNamespaceUri == namespaceUri) && segment.MatchCriteria(epmInfo.CriteriaValue, epmInfo.Criteria)); if (foundSegment != null) { currentSegment = foundSegment; if (currentSegment.EpmInfo != null && currentSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty) { multiValueSegment = currentSegment; } } else { currentSegment = new EpmTargetPathSegment(targetSegment, namespaceUri, namespacePrefix, currentSegment); currentSegment.Criteria = epmInfo.Criteria; currentSegment.CriteriaValue = epmInfo.CriteriaValue; if (targetSegment[0] == '@') { activeSubSegments.Insert(0, currentSegment); } else { activeSubSegments.Add(currentSegment); } } activeSubSegments = currentSegment.SubSegments; } // If we're adding a multiValue property to already existing segment which maps to a non-multiValue property (no EpmInfo or one pointing to a non-multiValue property) // OR if we're adding a non-multiValue property to a segment which has multiValue in its path // we need to fail, since it's invalid to have multiValue property being mapped to the same top-level element as anything else. if ((epmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty && foundSegment != null && (foundSegment.EpmInfo == null || foundSegment.EpmInfo.MultiValueStatus != EntityPropertyMappingMultiValueStatus.MultiValueProperty)) || (epmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.None && multiValueSegment != null)) { EntityPropertyMappingInfo multiValuePropertyEpmInfo = epmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty ? epmInfo : multiValueSegment.EpmInfo; // Trying to map MultiValue property to the same target as something else which was already mapped there // It is ok to map to atom:category and atom:link elements with different sources only if the criteria values are different. if (epmInfo.CriteriaValue != null) { throw new ODataException(Strings.EpmTargetTree_MultiValueAndNormalPropertyMappedToTheSameConditionalTopLevelElement( multiValuePropertyEpmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name, EpmTargetTree.GetPropertyNameFromEpmInfo(multiValuePropertyEpmInfo), epmInfo.CriteriaValue)); } else { throw new ODataException(Strings.EpmTargetTree_MultiValueAndNormalPropertyMappedToTheSameTopLevelElement( multiValuePropertyEpmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name, EpmTargetTree.GetPropertyNameFromEpmInfo(multiValuePropertyEpmInfo))); } } Debug.Assert( epmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.None || epmInfo.IsSyndicationMapping, "Custom EPM mapping is not supported for multiValue properties."); // We only allow multiValues to map to ATOM constructs which can be repeated. if (epmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueItemProperty) { Debug.Assert( epmInfo.Attribute.TargetSyndicationItem != SyndicationItemProperty.CustomProperty, "Trying to add custom mapped property to a syndication target tree."); // Right now all the SyndicationItemProperty targets which do not have SyndicationParent.Entry are valid targets for multiValues // and all the ones which have SyndicationParent.Entry (Title, Updated etc.) are not valid targets for multiValues. if (epmInfo.SyndicationParent == EpmSyndicationParent.Entry) { throw new ODataException(Strings.EpmTargetTree_MultiValueMappedToNonRepeatableAtomElement( epmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name, EpmTargetTree.GetPropertyNameFromEpmInfo(epmInfo))); } } Debug.Assert( epmInfo.MultiValueStatus != EntityPropertyMappingMultiValueStatus.MultiValueProperty || targetSegments.Length == 1, "MultiValue property itself can only be mapped to the top-level element."); if (currentSegment.EpmInfo != null) { if (currentSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty) { Debug.Assert( epmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty, "MultiValue property values can't be mapped directly to the top-level element content (no such syndication mapping exists)."); // The info we're trying to add is a multiValue property, which would mean two multiValue properties trying to map to the same top-level element. // This can happen if the base type defines mapping for a multiValue property and the derived type defines it again // in which case we will try to add the derived type mapping again. // So we need to check that these properties are from the same source // It is ok to map to atom:category and atom:link elements with different sources only if the criteria values are different. if (epmInfo.Attribute.SourcePath != currentSegment.EpmInfo.Attribute.SourcePath) { if (epmInfo.CriteriaValue != null) { throw new ODataException(Strings.EpmTargetTree_TwoMultiValuePropertiesMappedToTheSameConditionalTopLevelElement( currentSegment.EpmInfo.Attribute.SourcePath, epmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name, epmInfo.CriteriaValue)); } else { throw new ODataException(Strings.EpmTargetTree_TwoMultiValuePropertiesMappedToTheSameTopLevelElement( currentSegment.EpmInfo.Attribute.SourcePath, epmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name)); } } Debug.Assert( !foundSegment.EpmInfo.DefiningTypesAreEqual(epmInfo), "Trying to add a multiValue property mapping for the same property on the same type twice. The souce tree should have prevented this from happening."); // If the sources are the same (and the types are different), we can safely overwrite the epmInfo // with the new one (which is for the derived type) // The epm info is stored below. } else { Debug.Assert( epmInfo.MultiValueStatus != EntityPropertyMappingMultiValueStatus.MultiValueProperty, "Only non-multiValue propeties should get here, we cover the rest above."); // Two EpmAttributes with same TargetName in the inheritance hierarchy throw new ODataException(Strings.EpmTargetTree_DuplicateEpmAttrsWithSameTargetName(EpmTargetTree.GetPropertyNameFromEpmInfo(currentSegment.EpmInfo), currentSegment.EpmInfo.DefiningType.Name, currentSegment.EpmInfo.Attribute.SourcePath, epmInfo.Attribute.SourcePath)); } } // Increment the number of properties for which KeepInContent is false if (!epmInfo.Attribute.KeepInContent) { if (epmInfo.IsAtomLinkMapping || epmInfo.IsAtomCategoryMapping) { this.countOfNonContentV3mappings++; } else { this.countOfNonContentV2mappings++; } } currentSegment.EpmInfo = epmInfo; // Mixed content is dis-allowed. Since root has no ancestor, pass in false for ancestorHasContent if (EpmTargetTree.HasMixedContent(this.NonSyndicationRoot, false)) { throw new ODataException(Strings.EpmTargetTree_InvalidTargetPath(targetPath)); } }