Skip to content

Commit

Permalink
Various updates including:
Browse files Browse the repository at this point in the history
- Rebase to tip of ODL master branch.
- Add e2e test ExpandedCustomerEntryOmitNullValuesTest with various cases exercising null values.
- Updates restore null value processing related to base-type properties navigation properties by odata.navigationlink annotations.
- Added output for comparison of omittedNullValues of true & false in same test cases.
- Added test cases for unknonwn(dynamic) properties to verify that no content are restored.
- Added test cases for expanded navigation link to verify that omitted properties are restored for non-null entity, and that no content are restored for null entity.
  • Loading branch information
biaol-odata committed Mar 14, 2018
1 parent 3e9e4e2 commit 7c1dba3
Show file tree
Hide file tree
Showing 9 changed files with 618 additions and 154 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ private void WriteProperty(
{
WriterValidationUtils.ValidatePropertyNotNull(property);

ODataValue value = property.ODataValue;

string propertyName = property.Name;

if (this.JsonLightOutputContext.MessageWriterSettings.Validations != ValidationKinds.None)
Expand All @@ -197,6 +199,19 @@ private void WriteProperty(
this.currentPropertyInfo = this.JsonLightOutputContext.PropertyCacheHandler.GetProperty(propertyName, owningType);
}

// Optimization for null values:
// If no validation is required, we don't need property serialization info and could try to skip writing null property right away
// If this property is top-level, we cannot optimize here due to backward-compatibility requirement for OData-6.x.
// For very wide and sparse outputs it allows to avoid a lot of dictionary lookups
bool isNullValue = (value == null || value is ODataNullValue);
if (isNullValue && omitNullValues)
{
if (!this.currentPropertyInfo.IsTopLevel && !this.MessageWriterSettings.ThrowIfTypeConflictsWithMetadata)
{
return;
}
}

WriterValidationUtils.ValidatePropertyDefined(this.currentPropertyInfo, this.MessageWriterSettings.ThrowOnUndeclaredPropertyForNonOpenType);

duplicatePropertyNameChecker.ValidatePropertyUniqueness(property);
Expand All @@ -208,8 +223,6 @@ private void WriteProperty(

WriteInstanceAnnotation(property, isTopLevel, currentPropertyInfo.MetadataType.IsUndeclaredProperty);

ODataValue value = property.ODataValue;

// handle ODataUntypedValue
ODataUntypedValue untypedValue = value as ODataUntypedValue;
if (untypedValue != null)
Expand All @@ -233,7 +246,7 @@ private void WriteProperty(
return;
}

if (value is ODataNullValue || value == null)
if (isNullValue)
{
this.WriteNullProperty(property, omitNullValues);
return;
Expand Down
28 changes: 23 additions & 5 deletions src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2284,26 +2284,29 @@ private void RestoreOmittedNullValues()
ODataResourceBase resource = resourceState.Resource;

IEnumerable<IEdmProperty> selectedProperties;

if (resourceState.SelectedProperties == SelectedPropertiesNode.EntireSubtree)
{
selectedProperties = edmStructuredType.DeclaredProperties;
selectedProperties = GetSelfAndBaseTypeProperties(edmStructuredType);
}
else
{ // Partial subtree. Combine navigation properties and selected properties at the node with distinct.
{
// Partial subtree. Combine navigation properties and selected properties at the node with distinct.
selectedProperties =
resourceState.SelectedProperties.GetSelectedNavigationProperties(edmStructuredType)
as IEnumerable<IEdmProperty>;
selectedProperties = selectedProperties.Concat(
resourceState.SelectedProperties.GetSelectedProperties(edmStructuredType).Values)
resourceState.SelectedProperties.GetSelectedProperties(edmStructuredType))
.Distinct();
}

foreach (IEdmProperty currentProperty in selectedProperties)
{
Debug.Assert(currentProperty.Type != null, "currentProperty.Type != null");
if (!currentProperty.Type.IsNullable)
if (!currentProperty.Type.IsNullable || currentProperty.PropertyKind == EdmPropertyKind.Navigation)
{
// Skip declared properties that are not null-able types.
// Skip declared properties that are not null-able types, and declared navigation properties
// which are specified using odata.navigationlink annotations whose absence stand for null values.
continue;
}

Expand Down Expand Up @@ -2344,6 +2347,21 @@ private void RestoreOmittedNullValues()
}
}

/// <summary>
/// Get all properties defined by the EDM structural type and its base types.
/// </summary>
/// <param name="edmStructuredType">The EDM structural type.</param>
/// <returns>All the properties of this type.</returns>
private static IEnumerable<IEdmProperty> GetSelfAndBaseTypeProperties(IEdmStructuredType edmStructuredType)
{
if (edmStructuredType == null)
{
return Enumerable.Empty<IEdmProperty>();
}

return edmStructuredType.DeclaredProperties.Concat(GetSelfAndBaseTypeProperties(edmStructuredType.BaseType));
}

/// <summary>
/// Add info resolved from context url to current scope.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions src/Microsoft.OData.Core/ODataMessageWriterSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ private void CopyFrom(ODataMessageWriterSettings other)
this.Version = other.Version;
this.OmitNullValues = other.OmitNullValues;
this.LibraryCompatibility = other.LibraryCompatibility;

this.validations = other.validations;
this.ThrowIfTypeConflictsWithMetadata = other.ThrowIfTypeConflictsWithMetadata;
this.ThrowOnDuplicatePropertyNames = other.ThrowOnDuplicatePropertyNames;
Expand Down
15 changes: 6 additions & 9 deletions src/Microsoft.OData.Core/SelectedPropertiesNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ internal sealed class SelectedPropertiesNode
/// <summary>An empty set of navigation properties to return when nothing is selected.</summary>
private static readonly IEnumerable<IEdmNavigationProperty> EmptyNavigationProperties = Enumerable.Empty<IEdmNavigationProperty>();

/// <summary>An empty dictionary of EDM properties to return when nothing is selected.</summary>
private static readonly Dictionary<string, IEdmProperty> EmptyEdmProperties = new Dictionary<string, IEdmProperty>(StringComparer.Ordinal);
/// <summary>An empty enumeration of EDM properties to return when nothing is selected.</summary>
private static readonly IEnumerable<IEdmProperty> EmptyEdmProperties = Enumerable.Empty<IEdmProperty>();

/// <summary>The type of the current node.</summary>
private readonly SelectionType selectionType;
Expand Down Expand Up @@ -343,7 +343,7 @@ internal IEnumerable<IEdmNavigationProperty> GetSelectedNavigationProperties(IEd
/// </summary>
/// <param name="structuredType">The current structured type.</param>
/// <returns>The selected properties at this node level.</returns>
internal IDictionary<string, IEdmProperty> GetSelectedProperties(IEdmStructuredType structuredType)
internal IEnumerable<IEdmProperty> GetSelectedProperties(IEdmStructuredType structuredType)
{
if (this.selectionType == SelectionType.Empty)
{
Expand All @@ -358,16 +358,13 @@ internal IDictionary<string, IEdmProperty> GetSelectedProperties(IEdmStructuredT

if (this.selectionType == SelectionType.EntireSubtree || this.hasWildcard)
{
return structuredType.DeclaredProperties.ToDictionary(sp => sp.Name, StringComparer.Ordinal);
return structuredType.DeclaredProperties;
}

Debug.Assert(this.selectedProperties != null, "selectedProperties != null");

IDictionary<string, IEdmProperty> selectedEdmProperties = this.selectedProperties
.Select(structuredType.FindProperty)
.ToDictionary(p => p.Name, StringComparer.Ordinal);

return selectedEdmProperties;
// Get declared properties selected, and filter out unrecognized properties.
return this.selectedProperties.Select(structuredType.FindProperty).OfType<IEdmProperty>();
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
// </copyright>
//---------------------------------------------------------------------

using System.Runtime.CompilerServices;

namespace Microsoft.Test.OData.Tests.Client.WriteJsonPayloadTests
{
using System;
Expand Down Expand Up @@ -145,6 +147,36 @@ public void ExpandedCustomerEntryTest()
}
}

/// <summary>
/// Write and read an expanded customer entry containing primitive, complex, collection of primitive/complex properties with null values
/// using omit-value=nulls Preference header.
/// </summary>
[TestMethod]
public void ExpandedCustomerEntryOmitNullValuesTest()
{
// Repeat with full and minimal metadata.
foreach (string mimeType in this.mimeTypes.GetRange(0, 2))
{
var settings = new ODataMessageWriterSettings();
settings.ODataUri = new ODataUri() {ServiceRoot = this.ServiceUri};
string rspRecvd = null;

var responseMessageWithModel = new StreamResponseMessage(new MemoryStream());
responseMessageWithModel.SetHeader("Content-Type", mimeType);
responseMessageWithModel.PreferenceAppliedHeader().OmitValues = ODataConstants.OmitValuesNulls;

using (var messageWriter = new ODataMessageWriter(responseMessageWithModel, settings,
WritePayloadHelper.Model))
{
var odataWriter = messageWriter.CreateODataResourceWriter(WritePayloadHelper.CustomerSet,
WritePayloadHelper.CustomerType);
rspRecvd = this.WriteAndVerifyExpandedCustomerEntryWithSomeNullValues(
responseMessageWithModel, odataWriter, /* hasModel */false);
Assert.IsNotNull(rspRecvd);
}
}
}

/// <summary>
/// Write an entry containing stream, named stream
/// </summary>
Expand Down Expand Up @@ -519,10 +551,9 @@ private string WriteAndVerifyOrderFeed(StreamResponseMessage responseMessage, OD
return WritePayloadHelper.ReadStreamContent(stream);
}

private string WriteAndVerifyExpandedCustomerEntry(StreamResponseMessage responseMessage,
ODataWriter odataWriter, bool hasModel, string mimeType)
private void WriteCustomerEntry( ODataWriter odataWriter, bool hasModel, bool withSomeNullValues)
{
ODataResourceWrapper customerEntry = WritePayloadHelper.CreateCustomerEntry(hasModel);
ODataResourceWrapper customerEntry = WritePayloadHelper.CreateCustomerEntry(hasModel, withSomeNullValues);

var loginFeed = new ODataResourceSet() { Id = new Uri(this.ServiceUri + "Customer(-9)/Logins") };
if (!hasModel)
Expand All @@ -533,30 +564,41 @@ private string WriteAndVerifyExpandedCustomerEntry(StreamResponseMessage respons
var loginEntry = WritePayloadHelper.CreateLoginEntry(hasModel);


customerEntry.NestedResourceInfoWrappers = customerEntry.NestedResourceInfoWrappers.Concat(WritePayloadHelper.CreateCustomerNavigationLinks());
customerEntry.NestedResourceInfoWrappers = customerEntry.NestedResourceInfoWrappers.Concat(new[]{ new ODataNestedResourceInfoWrapper()
{
NestedResourceInfo = new ODataNestedResourceInfo()
{
Name = "Logins",
IsCollection = true,
Url = new Uri(this.ServiceUri + "Customer(-9)/Logins")
},
NestedResourceOrResourceSet = new ODataResourceSetWrapper()
{
ResourceSet = loginFeed,
Resources = new List<ODataResourceWrapper>()
{
new ODataResourceWrapper()
// Setting null values for some navigation links, those null links won't be serialized
// as odata.navigationlink annotations.
customerEntry.NestedResourceInfoWrappers = customerEntry.NestedResourceInfoWrappers.Concat(
WritePayloadHelper.CreateCustomerNavigationLinks(withSomeNullValues));
customerEntry.NestedResourceInfoWrappers = customerEntry.NestedResourceInfoWrappers.Concat(
new[]{ new ODataNestedResourceInfoWrapper()
{
Resource = loginEntry,
NestedResourceInfoWrappers = WritePayloadHelper.CreateLoginNavigationLinksWrapper().ToList()
NestedResourceInfo = new ODataNestedResourceInfo()
{
Name = "Logins",
IsCollection = true,
Url = new Uri(this.ServiceUri + "Customer(-9)/Logins")
},
NestedResourceOrResourceSet = new ODataResourceSetWrapper()
{
ResourceSet = loginFeed,
Resources = new List<ODataResourceWrapper>()
{
new ODataResourceWrapper()
{
Resource = loginEntry,
NestedResourceInfoWrappers = WritePayloadHelper.CreateLoginNavigationLinksWrapper(withSomeNullValues).ToList()
}
}
}
}
}
}
}});
});

ODataWriterHelper.WriteResource(odataWriter, customerEntry);
}

private string WriteAndVerifyExpandedCustomerEntry(StreamResponseMessage responseMessage,
ODataWriter odataWriter, bool hasModel, string mimeType)
{
WriteCustomerEntry(odataWriter, hasModel, false);

// Some very basic verification for the payload.
bool verifyFeedCalled = false;
Expand Down Expand Up @@ -601,6 +643,57 @@ private string WriteAndVerifyExpandedCustomerEntry(StreamResponseMessage respons
return WritePayloadHelper.ReadStreamContent(stream);
}

private string WriteAndVerifyExpandedCustomerEntryWithSomeNullValues(StreamResponseMessage responseMessage,
ODataWriter odataWriter, bool hasModel)
{
WriteCustomerEntry(odataWriter, hasModel, /* withSomeNullValues */true);

// Some very basic verification for the payload.
bool verifyFeedCalled = false;
int verifyEntryCalled = 0;
int verifyNullValuesCount = 0;
bool verifyNavigationCalled = false;
Action<ODataResourceSet> verifyFeed = (feed) =>
{
verifyFeedCalled = true;
};

Action<ODataResource> verifyEntry = (entry) =>
{
if (entry.TypeName.Contains("Customer"))
{
Assert.AreEqual(4, entry.Properties.Count());
verifyEntryCalled++;
}

if (entry.TypeName.Contains("Login"))
{
Assert.AreEqual(2, entry.Properties.Count());
verifyEntryCalled++;
}

// Counting restored null property value during response de-serialization.
verifyNullValuesCount += entry.Properties.Count(p => p.Value == null);
};

Action<ODataNestedResourceInfo> verifyNavigation = (navigation) =>
{
Assert.IsNotNull(navigation.Name);
verifyNavigationCalled = true;
};

Stream stream = responseMessage.GetStream();
stream.Seek(0, SeekOrigin.Begin);
WritePayloadHelper.ReadAndVerifyFeedEntryMessage(false, responseMessage, WritePayloadHelper.CustomerSet, WritePayloadHelper.CustomerType,
verifyFeed, verifyEntry, verifyNavigation);
Assert.IsTrue(verifyFeedCalled);
Assert.IsTrue(verifyEntryCalled == 2);
Assert.IsTrue(verifyNullValuesCount == 4);
Assert.IsTrue(verifyNavigationCalled);

return WritePayloadHelper.ReadStreamContent(stream);
}

private string WriteAndVerifyCarEntry(StreamResponseMessage responseMessage, ODataWriter odataWriter,
bool hasModel, string mimeType)
{
Expand Down
Loading

0 comments on commit 7c1dba3

Please sign in to comment.