diff --git a/src/Squidex.Domain.Apps.Read/State/Orleans/AppStateEventConsumer.cs b/src/Squidex.Domain.Apps.Read/State/Orleans/AppStateEventConsumer.cs index 2b136236e..84b485b1c 100644 --- a/src/Squidex.Domain.Apps.Read/State/Orleans/AppStateEventConsumer.cs +++ b/src/Squidex.Domain.Apps.Read/State/Orleans/AppStateEventConsumer.cs @@ -1,5 +1,5 @@ // ========================================================================== -// StateEventConsumer.cs +// AppStateEventConsumer.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -8,12 +8,12 @@ using System.Threading.Tasks; using Orleans; -using Orleans.Concurrency; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Apps; using Squidex.Domain.Apps.Read.State.Orleans.Grains; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Events; +using Squidex.Infrastructure.Json.Orleans; using Squidex.Infrastructure.Tasks; namespace Squidex.Domain.Apps.Read.State.Orleans @@ -50,7 +50,7 @@ namespace Squidex.Domain.Apps.Read.State.Orleans { var appGrain = factory.GetGrain(appEvent.AppId.Name); - await appGrain.HandleAsync(new Immutable>(@event)); + await appGrain.HandleAsync(@event.AsJ()); } if (@event.Payload is AppContributorAssigned contributorAssigned) diff --git a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/IAppStateGrain.cs b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/IAppStateGrain.cs index d2d3676dc..e3bfe27ee 100644 --- a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/IAppStateGrain.cs +++ b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/IAppStateGrain.cs @@ -10,28 +10,28 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Orleans; -using Orleans.Concurrency; using Squidex.Domain.Apps.Read.Apps; using Squidex.Domain.Apps.Read.Rules; using Squidex.Domain.Apps.Read.Schemas; using Squidex.Infrastructure.CQRS.Events; +using Squidex.Infrastructure.Json.Orleans; namespace Squidex.Domain.Apps.Read.State.Orleans.Grains { public interface IAppStateGrain : IGrainWithStringKey { - Task> GetAppWithSchemaAsync(Guid id); + Task> GetAppWithSchemaAsync(Guid id); - Task> GetAppAsync(); + Task> GetAppAsync(); - Task> GetSchemaAsync(Guid id, bool provideDeleted = false); + Task> GetSchemaAsync(Guid id, bool provideDeleted = false); - Task> GetSchemaAsync(string name, bool provideDeleted = false); + Task> GetSchemaAsync(string name, bool provideDeleted = false); - Task>> GetSchemasAsync(); + Task>> GetSchemasAsync(); - Task>> GetRulesAsync(); + Task>> GetRulesAsync(); - Task HandleAsync(Immutable> message); + Task HandleAsync(J> message); } } diff --git a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrain.cs b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrain.cs index 139ee1887..b964d8009 100644 --- a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrain.cs +++ b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrain.cs @@ -9,7 +9,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Orleans.Concurrency; using Orleans.Runtime; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Events.Apps; @@ -18,6 +17,7 @@ using Squidex.Domain.Apps.Read.Rules; using Squidex.Domain.Apps.Read.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Events; +using Squidex.Infrastructure.Json.Orleans; using Squidex.Infrastructure.Orleans; namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations @@ -57,49 +57,49 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations return base.OnActivateAsync(); } - public Task> GetAppWithSchemaAsync(Guid id) + public Task> GetAppWithSchemaAsync(Guid id) { var schema = State.FindSchema(x => x.Id == id && !x.IsDeleted); - return Task.FromResult((State.GetApp(), schema).AsImmutable()); + return Task.FromResult((State.GetApp(), schema).AsJ()); } - public Task> GetAppAsync() + public Task> GetAppAsync() { var value = State.GetApp(); - return Task.FromResult(value.AsImmutable()); + return Task.FromResult(value.AsJ()); } - public Task>> GetRulesAsync() + public Task>> GetRulesAsync() { var value = State.FindRules(); - return Task.FromResult(value.AsImmutable()); + return Task.FromResult(value.AsJ()); } - public Task>> GetSchemasAsync() + public Task>> GetSchemasAsync() { var value = State.FindSchemas(x => !x.IsDeleted); - return Task.FromResult(value.AsImmutable()); + return Task.FromResult(value.AsJ()); } - public Task> GetSchemaAsync(Guid id, bool provideDeleted = false) + public Task> GetSchemaAsync(Guid id, bool provideDeleted = false) { var value = State.FindSchema(x => x.Id == id && (!x.IsDeleted || provideDeleted)); - return Task.FromResult(value.AsImmutable()); + return Task.FromResult(value.AsJ()); } - public Task> GetSchemaAsync(string name, bool provideDeleted = false) + public Task> GetSchemaAsync(string name, bool provideDeleted = false) { var value = State.FindSchema(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && (!x.IsDeleted || provideDeleted)); - return Task.FromResult(value.AsImmutable()); + return Task.FromResult(value.AsJ()); } - public Task HandleAsync(Immutable> message) + public Task HandleAsync(J> message) { if (exception != null) { diff --git a/src/Squidex.Infrastructure/Json/Orleans/IJsonValue.cs b/src/Squidex.Infrastructure/Json/Orleans/IJsonValue.cs new file mode 100644 index 000000000..f2d8ab41f --- /dev/null +++ b/src/Squidex.Infrastructure/Json/Orleans/IJsonValue.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// IJsonValue.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +namespace Squidex.Infrastructure.Json.Orleans +{ + public interface IJsonValue + { + object Value { get; } + + bool IsImmutable { get; } + } +} diff --git a/src/Squidex.Infrastructure/Json/Orleans/J.cs b/src/Squidex.Infrastructure/Json/Orleans/J.cs new file mode 100644 index 000000000..c137b6a8d --- /dev/null +++ b/src/Squidex.Infrastructure/Json/Orleans/J.cs @@ -0,0 +1,57 @@ +// ========================================================================== +// J.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace Squidex.Infrastructure.Json.Orleans +{ + public struct J : IJsonValue + { + private readonly T value; + private readonly bool isImmutable; + + public T Value + { + get { return value; } + } + + bool IJsonValue.IsImmutable + { + get { return isImmutable; } + } + + object IJsonValue.Value + { + get { return Value; } + } + + [JsonConstructor] + public J(T value, bool isImmutable = false) + { + this.value = value; + + this.isImmutable = isImmutable; + } + + public static implicit operator T(J value) + { + return value.Value; + } + + public static implicit operator J(T d) + { + return new J(d); + } + + public static Task> AsTask(T value) + { + return Task.FromResult>(value); + } + } +} diff --git a/src/Squidex.Infrastructure/Json/Orleans/JExtensions.cs b/src/Squidex.Infrastructure/Json/Orleans/JExtensions.cs new file mode 100644 index 000000000..daf6fcb52 --- /dev/null +++ b/src/Squidex.Infrastructure/Json/Orleans/JExtensions.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// JExtensions.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +namespace Squidex.Infrastructure.Json.Orleans +{ + public static class JExtensions + { + public static J AsJ(this T value, bool immutable = true) + { + return new J(value, immutable); + } + } +} diff --git a/src/Squidex.Infrastructure/Json/Orleans/JsonExternalSerializer.cs b/src/Squidex.Infrastructure/Json/Orleans/JsonExternalSerializer.cs index a6806add5..80566b15d 100644 --- a/src/Squidex.Infrastructure/Json/Orleans/JsonExternalSerializer.cs +++ b/src/Squidex.Infrastructure/Json/Orleans/JsonExternalSerializer.cs @@ -7,8 +7,8 @@ // ========================================================================== using System; -using System.Collections.Generic; using System.IO; +using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Orleans.Runtime; @@ -19,15 +19,12 @@ namespace Squidex.Infrastructure.Json.Orleans public class JsonExternalSerializer : IExternalSerializer { private readonly JsonSerializer serializer; - private readonly HashSet types; - public JsonExternalSerializer(JsonSerializer serializer, params Type[] types) + public JsonExternalSerializer(JsonSerializer serializer) { Guard.NotNull(serializer, nameof(serializer)); this.serializer = serializer; - - this.types = new HashSet(types); } public void Initialize(Logger logger) @@ -36,15 +33,25 @@ namespace Squidex.Infrastructure.Json.Orleans public bool IsSupportedType(Type itemType) { - return types.Contains(itemType); + return itemType.GetInterfaces().Contains(typeof(IJsonValue)); } public object DeepCopy(object source, ICopyContext context) { - if (source == null) + var jsonValue = source as IJsonValue; + + if (jsonValue == null) { return null; } + else if (jsonValue.IsImmutable) + { + return jsonValue; + } + else if (jsonValue.Value == null) + { + return jsonValue; + } else { return JObject.FromObject(source, serializer).ToObject(source.GetType(), serializer); diff --git a/src/Squidex/Config/Orleans/CustomJsonSerializer.cs b/src/Squidex/Config/Orleans/CustomJsonSerializer.cs index 8fe2e5e3f..7fd8334ce 100644 --- a/src/Squidex/Config/Orleans/CustomJsonSerializer.cs +++ b/src/Squidex/Config/Orleans/CustomJsonSerializer.cs @@ -8,7 +8,6 @@ using Newtonsoft.Json; using Squidex.Config.Domain; -using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.Json.Orleans; namespace Squidex.Config.Orleans @@ -16,8 +15,7 @@ namespace Squidex.Config.Orleans public class CustomJsonSerializer : JsonExternalSerializer { public CustomJsonSerializer() - : base(JsonSerializer.Create(SerializationServices.DefaultJsonSettings), - typeof(Envelope)) + : base(JsonSerializer.Create(SerializationServices.DefaultJsonSettings)) { } } diff --git a/tests/Squidex.Infrastructure.Tests/Json/Orleans/JsonExternalSerializerTests.cs b/tests/Squidex.Infrastructure.Tests/Json/Orleans/JsonExternalSerializerTests.cs index 1949209a6..13f0ad5cd 100644 --- a/tests/Squidex.Infrastructure.Tests/Json/Orleans/JsonExternalSerializerTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Json/Orleans/JsonExternalSerializerTests.cs @@ -22,38 +22,54 @@ namespace Squidex.Infrastructure.Json.Orleans [Fact] public void Should_serialize_js_only() { - var serializer = new JsonExternalSerializer(JsonSerializer.CreateDefault(), typeof(int), typeof(bool)); + Assert.True(sut.IsSupportedType(typeof(J))); + Assert.True(sut.IsSupportedType(typeof(J>))); - Assert.True(sut.IsSupportedType(typeof(int))); - Assert.True(sut.IsSupportedType(typeof(bool))); - - Assert.False(sut.IsSupportedType(typeof(float))); - Assert.False(sut.IsSupportedType(typeof(double))); + Assert.False(sut.IsSupportedType(typeof(int))); + Assert.False(sut.IsSupportedType(typeof(List))); } [Fact] public void Should_copy_null() { - var value = (string)null; - var copy = sut.DeepCopy(value, null); + var v = (string)null; + var c = DeepCopy(v); + + Assert.Null(c); + } + + [Fact] + public void Should_copy_null_json() + { + var v = new J>(null); + var c = DeepCopy(v); + + Assert.Null(c.Value); + } + + [Fact] + public void Should_not_copy_immutable_values() + { + var v = new List { 1, 2, 3 }.AsJ(true); + var c = DeepCopy(v); - Assert.Null(copy); + Assert.Same(v.Value, c.Value); } [Fact] public void Should_copy_non_immutable_values() { - var value = new List { 1, 2, 3 }; - var copy = (List)sut.DeepCopy(value, null); + var value = new J>(new List { 1, 2, 3 }); + var copy = (J>)sut.DeepCopy(value, null); - Assert.Equal(value, copy); - Assert.NotSame(value, copy); + Assert.Equal(value.Value, copy.Value); + Assert.NotSame(value.Value, copy.Value); } [Fact] public void Should_serialize_and_deserialize_value() { - var value = new List(new List { 1, 2, 3 }); + var value = new J>(new List { 1, 2, 3 }); var writtenLength = 0; var writtenBuffer = (byte[])null; @@ -78,10 +94,15 @@ namespace Squidex.Infrastructure.Json.Orleans A.CallTo(() => reader.ReadBytes(writtenLength)) .Returns(writtenBuffer); - var copy = (List)sut.Deserialize(value.GetType(), readerContext); + var copy = (J>)sut.Deserialize(value.GetType(), readerContext); - Assert.Equal(value, copy); - Assert.NotSame(value, copy); + Assert.Equal(value.Value, copy.Value); + Assert.NotSame(value.Value, copy.Value); + } + + private T DeepCopy(T value) + { + return (T)sut.DeepCopy(value, null); } } }