Skip to content

Commit

Permalink
Add computed and immutable annotation support
Browse files Browse the repository at this point in the history
  • Loading branch information
chinadragon0515 committed Aug 8, 2016
1 parent a6db7ca commit b83c4c8
Show file tree
Hide file tree
Showing 14 changed files with 4,119 additions and 1,362 deletions.
4 changes: 4 additions & 0 deletions src/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.Model.RestierModelExtender+ModelMapper.#InnerModelMapper")]
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.BaseResult.#EdmType")]
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.BaseResult.#Context")]
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.PropertyAttributes.#NoWritePermission")]
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.PropertyAttributes.#NoReadPermission")]
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.PropertyAttributes.#NoReadPermission")]
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Restier.Publishers.OData.PropertyAttributes.#NoWritePermission")]
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Ensure.#NotNull`1(System.Nullable`1<!!0>,System.String)")]
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelperMethods.#EnumerableCastGeneric")]
[assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "System.Linq.Expressions.ExpressionHelperMethods.#EnumerableToListGeneric")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public async Task InitializeAsync(
// This means request resource is sub type of resource type
if (entry.ActualResourceType != null && resourceType != entry.ActualResourceType)
{
// Set type to derived type
resourceType = entry.ActualResourceType;
}

Expand All @@ -55,9 +56,7 @@ public async Task InitializeAsync(
if (entry.DataModificationItemAction == DataModificationItemAction.Insert)
{
resource = set.Create();

SetValues(resource, resourceType, entry.LocalValues);

set.Add(resource);
}
else if (entry.DataModificationItemAction == DataModificationItemAction.Remove)
Expand Down
96 changes: 94 additions & 2 deletions src/Microsoft.Restier.Publishers.OData/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Annotations;
using Microsoft.OData.Edm.Library;
using Microsoft.OData.Edm.Library.Annotations;
using Microsoft.OData.Edm.Library.Values;
using Microsoft.OData.Edm.Vocabularies.V1;
using Microsoft.Restier.Core;
using Microsoft.Restier.Publishers.OData.Model;
Expand All @@ -29,6 +31,10 @@ internal static class Extensions
private static ConcurrentDictionary<IEdmEntitySet, bool> concurrencyCheckFlags
= new ConcurrentDictionary<IEdmEntitySet, bool>();

private static ConcurrentDictionary<IEdmStructuredType, IDictionary<string, PropertyAttributes>>
typePropertiesAttributes
= new ConcurrentDictionary<IEdmStructuredType, IDictionary<string, PropertyAttributes>>();

public static void ApplyTo(this ETag etag, IDictionary<string, object> propertyValues)
{
if (etag != null)
Expand Down Expand Up @@ -63,18 +69,31 @@ public static bool IsConcurrencyCheckEnabled(this IEdmModel model, IEdmEntitySet
return needCurrencyCheck;
}

public static IReadOnlyDictionary<string, object> CreatePropertyDictionary(this Delta entity)
public static IReadOnlyDictionary<string, object> CreatePropertyDictionary(
this Delta entity, IEdmStructuredType edmType, ApiBase api, bool isCreation)
{
var propertiesAttributes = RetrievePropertiesAttributes(edmType, api);

Dictionary<string, object> propertyValues = new Dictionary<string, object>();
foreach (string propertyName in entity.GetChangedPropertyNames())
{
PropertyAttributes attributes;
if (propertiesAttributes != null && propertiesAttributes.TryGetValue(propertyName, out attributes))
{
if ((isCreation && attributes.IgnoreForCreation) || (!isCreation && attributes.IgnoreForUpdate))
{
// Will not get the properties for update or creation
continue;
}
}

object value;
if (entity.TryGetPropertyValue(propertyName, out value))
{
var complexObj = value as EdmComplexObject;
if (complexObj != null)
{
value = CreatePropertyDictionary(complexObj);
value = CreatePropertyDictionary(complexObj, complexObj.ActualEdmType, api, isCreation);
}

propertyValues.Add(propertyName, value);
Expand All @@ -84,6 +103,79 @@ public static IReadOnlyDictionary<string, object> CreatePropertyDictionary(this
return propertyValues;
}

public static IDictionary<string, PropertyAttributes> RetrievePropertiesAttributes(
IEdmStructuredType edmType, ApiBase api)
{
IDictionary<string, PropertyAttributes> propertiesAttributes;
if (typePropertiesAttributes.TryGetValue(edmType, out propertiesAttributes))
{
return propertiesAttributes;
}

var model = api.Context.GetModelAsync().Result;
foreach (var property in edmType.DeclaredProperties)
{
var annotations = model.FindVocabularyAnnotations(property);
PropertyAttributes attributes = null;
foreach (var annotation in annotations)
{
var valueAnnotation = annotation as EdmAnnotation;
if (valueAnnotation == null)
{
continue;
}

if (valueAnnotation.Term.Namespace == CoreVocabularyModel.ImmutableTerm.Namespace
&& valueAnnotation.Term.Name == CoreVocabularyModel.ImmutableTerm.Name)
{
var value = valueAnnotation.Value as EdmBooleanConstant;
if (value != null && value.Value)
{
if (attributes == null)
{
attributes = new PropertyAttributes();
}

attributes.IgnoreForUpdate = true;
}
}

if (valueAnnotation.Term.Namespace == CoreVocabularyModel.ComputedTerm.Namespace
&& valueAnnotation.Term.Name == CoreVocabularyModel.ComputedTerm.Name)
{
var value = valueAnnotation.Value as EdmBooleanConstant;
if (value != null && value.Value)
{
if (attributes == null)
{
attributes = new PropertyAttributes();
}

attributes.IgnoreForUpdate = true;
attributes.IgnoreForCreation = true;
}
}

// TODO add permission annotation check
// CoreVocabularyModel has no permission yet, will add with #480
}

// Add property attributes to the dictionary
if (attributes != null)
{
if (propertiesAttributes == null)
{
propertiesAttributes = new Dictionary<string, PropertyAttributes>();
typePropertiesAttributes[edmType] = propertiesAttributes;
}

propertiesAttributes.Add(property.Name, attributes);
}
}

return propertiesAttributes;
}

public static Type GetClrType(this IEdmType edmType, ApiBase api)
{
IEdmModel edmModel = api.GetModelAsync().Result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
<Compile Include="Model\ApiConfigurationExtensions.cs" />
<Compile Include="Model\ModelMapper.cs" />
<Compile Include="Model\OperationAttribute.cs" />
<Compile Include="Model\PropertyAttributes.cs" />
<Compile Include="Model\RestierModelBuilder.cs" />
<Compile Include="Model\RestierModelExtender.cs" />
<Compile Include="Model\RestierOperationModelBuilder.cs" />
Expand Down
28 changes: 28 additions & 0 deletions src/Microsoft.Restier.Publishers.OData/Model/PropertyAttributes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

namespace Microsoft.Restier.Publishers.OData
{
internal class PropertyAttributes
{
/// <summary>
/// Gets or sets a value indicating whether the property should be ignored during update
/// </summary>
public bool IgnoreForUpdate { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the property should be ignored during creation
/// </summary>
public bool IgnoreForCreation { get; set; }

/// <summary>
/// Gets or sets a value indicating whether there is permission to read the property
/// </summary>
public bool NoReadPermission { get; set; }

/// <summary>
/// Gets or sets a value indicating whether there is permission to write the property
/// </summary>
public bool NoWritePermission { get; set; }
}
}
16 changes: 10 additions & 6 deletions src/Microsoft.Restier.Publishers.OData/RestierController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public async Task<IHttpActionResult> Post(EdmEntityObject edmEntityObject, Cance

// In case of type inheritance, the actual type will be different from entity type
var expectedEntityType = path.EdmType;
var actualEntityType = path.EdmType;
var actualEntityType = path.EdmType as IEdmStructuredType;
if (edmEntityObject.ActualEdmType != null)
{
expectedEntityType = edmEntityObject.ExpectedEdmType;
Expand All @@ -163,7 +163,7 @@ public async Task<IHttpActionResult> Post(EdmEntityObject edmEntityObject, Cance
DataModificationItemAction.Insert,
null,
null,
edmEntityObject.CreatePropertyDictionary());
edmEntityObject.CreatePropertyDictionary(actualEntityType, api, true));

RestierChangeSetProperty changeSetProperty = this.Request.GetChangeSet();
if (changeSetProperty == null)
Expand Down Expand Up @@ -384,13 +384,17 @@ private async Task<IHttpActionResult> Update(
var propertiesInEtag = await this.GetOriginalValues(entitySet);
if (propertiesInEtag == null)
{
throw new PreconditionRequiredException(Resources.PreconditionRequired);
throw new PreconditionRequiredException(Resources.PreconditionRequired);
}

// In case of type inheritance, the actual type will be different from entity type
// This is only needed for put case, and does not for patch case
// This is only needed for put case, and does not need for patch case
// For put request, it will create a new, blank instance of the entity.
// copy over the key values and set any updated values from the client on the new instance.
// Then apply all the properties of the new instance to the instance to be updated.
// This will set any unspecified properties to their default value.
var expectedEntityType = path.EdmType;
var actualEntityType = path.EdmType;
var actualEntityType = path.EdmType as IEdmStructuredType;
if (edmEntityObject.ActualEdmType != null)
{
expectedEntityType = edmEntityObject.ExpectedEdmType;
Expand All @@ -404,7 +408,7 @@ private async Task<IHttpActionResult> Update(
DataModificationItemAction.Update,
RestierQueryBuilder.GetPathKeyValues(path),
propertiesInEtag,
edmEntityObject.CreatePropertyDictionary());
edmEntityObject.CreatePropertyDictionary(actualEntityType, api, false));
updateItem.IsFullReplaceUpdateRequest = isFullReplaceUpdate;

RestierChangeSetProperty changeSetProperty = this.Request.GetChangeSet();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Timers;

Expand All @@ -11,23 +10,29 @@ namespace Microsoft.OData.Service.Library.DataStoreManager
/// <summary>
/// Default resource management class to manage resources.
/// Use a dictionary to easily access the resource by <see cref="TKey"/> and make a constraint on the total number of resources.
/// Use a timer for each reasource, when the resource live longer than <see cref="MaxDataStoreInstanceLifeTime"/>, it will be destroyed automatically.
/// Use a timer for each resource, when the resource live longer than <see cref="MaxDataStoreInstanceLifeTime"/>, it will be destroyed automatically.
/// </summary>
public class DefaultDataStoreManager<TKey, TDataStoreType> :IDataStoreManager<TKey, TDataStoreType> where TDataStoreType : class, new()
{
/// <summary>
/// The max capacity of the resource container, this is a constraint for memory cost.
/// </summary>
public int MaxDataStoreInstanceCapacity { get; set; } = 1000;
public int MaxDataStoreInstanceCapacity { get; set; }

/// <summary>
/// The max life time of each resource. When the resource lives longer than that, it will be destroyed automatically.
/// Besides, when the resource container is full, the resource live longest will be destroyed.
/// </summary>
public TimeSpan MaxDataStoreInstanceLifeTime { get; set; } = new TimeSpan(0, 15, 0);
public TimeSpan MaxDataStoreInstanceLifeTime { get; set; }

private Dictionary<TKey, DataStoreUnit> _dataStoreDict = new Dictionary<TKey, DataStoreUnit>();

public DefaultDataStoreManager()
{
MaxDataStoreInstanceCapacity = 1000;
MaxDataStoreInstanceLifeTime = new TimeSpan(0, 15, 0);
}

public TDataStoreType ResetDataStoreInstance(TKey key)
{
if (_dataStoreDict.ContainsKey(key))
Expand Down
Loading

0 comments on commit b83c4c8

Please sign in to comment.