diff --git a/src/Squidex.Domain.Apps.Core.Model/Rules/Actions/WebhookAction.cs b/src/Squidex.Domain.Apps.Core.Model/Rules/Actions/WebhookAction.cs index 09718ad8f..974855340 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Rules/Actions/WebhookAction.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Rules/Actions/WebhookAction.cs @@ -14,9 +14,36 @@ namespace Squidex.Domain.Apps.Core.Rules.Actions [TypeName(nameof(WebhookAction))] public sealed class WebhookAction : RuleAction { - public Uri Url { get; set; } + private Uri url; + private string sharedSecret; - public string SharedSecret { get; set; } + public Uri Url + { + get + { + return url; + } + set + { + ThrowIfFrozen(); + + url = value; + } + } + + public string SharedSecret + { + get + { + return sharedSecret; + } + set + { + ThrowIfFrozen(); + + sharedSecret = value; + } + } public override T Accept(IRuleActionVisitor visitor) { diff --git a/src/Squidex.Domain.Apps.Core.Model/Rules/Rule.cs b/src/Squidex.Domain.Apps.Core.Model/Rules/Rule.cs index 8595d84a8..1e7a900b7 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Rules/Rule.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Rules/Rule.cs @@ -11,7 +11,7 @@ using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.Rules { - public sealed class Rule + public sealed class Rule : Cloneable { private RuleTrigger trigger; private RuleAction action; @@ -41,17 +41,23 @@ namespace Squidex.Domain.Apps.Core.Rules this.action = action; } - public void Enable() + public Rule Enable() { - this.isEnabled = true; + return Clone(clone => + { + clone.isEnabled = true; + }); } - public void Disable() + public Rule Disable() { - this.isEnabled = false; + return Clone(clone => + { + clone.isEnabled = false; + }); } - public void Update(RuleTrigger newTrigger) + public Rule Update(RuleTrigger newTrigger) { Guard.NotNull(newTrigger, nameof(newTrigger)); @@ -60,10 +66,13 @@ namespace Squidex.Domain.Apps.Core.Rules throw new ArgumentException("New trigger has another type.", nameof(newTrigger)); } - trigger = newTrigger; + return Clone(clone => + { + clone.trigger = newTrigger; + }); } - public void Update(RuleAction newAction) + public Rule Update(RuleAction newAction) { Guard.NotNull(newAction, nameof(newAction)); @@ -72,7 +81,10 @@ namespace Squidex.Domain.Apps.Core.Rules throw new ArgumentException("New action has another type.", nameof(newAction)); } - action = newAction; + return Clone(clone => + { + clone.action = newAction; + }); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Rules/RuleAction.cs b/src/Squidex.Domain.Apps.Core.Model/Rules/RuleAction.cs index 483abdff9..d2a5ccfc8 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Rules/RuleAction.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Rules/RuleAction.cs @@ -6,9 +6,11 @@ // All rights reserved. // ========================================================================== +using Squidex.Infrastructure; + namespace Squidex.Domain.Apps.Core.Rules { - public abstract class RuleAction + public abstract class RuleAction : Freezable { public abstract T Accept(IRuleActionVisitor visitor); } diff --git a/src/Squidex.Domain.Apps.Core.Model/Rules/RuleTrigger.cs b/src/Squidex.Domain.Apps.Core.Model/Rules/RuleTrigger.cs index 15284960c..9f996553f 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Rules/RuleTrigger.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Rules/RuleTrigger.cs @@ -6,9 +6,11 @@ // All rights reserved. // ========================================================================== +using Squidex.Infrastructure; + namespace Squidex.Domain.Apps.Core.Rules { - public abstract class RuleTrigger + public abstract class RuleTrigger : Freezable { public abstract T Accept(IRuleTriggerVisitor visitor); } diff --git a/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTrigger.cs b/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTrigger.cs index cc11d969c..7f8691725 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTrigger.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTrigger.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -using System.Collections.Generic; +using System.Collections.Immutable; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.Rules.Triggers @@ -14,7 +14,21 @@ namespace Squidex.Domain.Apps.Core.Rules.Triggers [TypeName(nameof(ContentChangedTrigger))] public sealed class ContentChangedTrigger : RuleTrigger { - public List Schemas { get; set; } + private ImmutableList schemas; + + public ImmutableList Schemas + { + get + { + return schemas; + } + set + { + ThrowIfFrozen(); + + schemas = value; + } + } public override T Accept(IRuleTriggerVisitor visitor) { diff --git a/src/Squidex.Domain.Apps.Events/Rules/Utils/RuleEventDispatcher.cs b/src/Squidex.Domain.Apps.Events/Rules/Utils/RuleEventDispatcher.cs index 8de79c181..e85f4eecf 100644 --- a/src/Squidex.Domain.Apps.Events/Rules/Utils/RuleEventDispatcher.cs +++ b/src/Squidex.Domain.Apps.Events/Rules/Utils/RuleEventDispatcher.cs @@ -17,27 +17,29 @@ namespace Squidex.Domain.Apps.Events.Rules.Utils return new Rule(@event.Trigger, @event.Action); } - public static void Apply(this Rule rule, RuleUpdated @event) + public static Rule Apply(this Rule rule, RuleUpdated @event) { if (@event.Trigger != null) { - rule.Update(@event.Trigger); + return rule.Update(@event.Trigger); } if (@event.Action != null) { - rule.Update(@event.Action); + return rule.Update(@event.Action); } + + return rule; } - public static void Apply(this Rule rule, RuleEnabled @event) + public static Rule Apply(this Rule rule, RuleEnabled @event) { - rule.Enable(); + return rule.Enable(); } - public static void Apply(this Rule rule, RuleDisabled @event) + public static Rule Apply(this Rule rule, RuleDisabled @event) { - rule.Disable(); + return rule.Disable(); } } } diff --git a/src/Squidex.Domain.Apps.Events/Schemas/Utils/SchemaEventDispatcher.cs b/src/Squidex.Domain.Apps.Events/Schemas/Utils/SchemaEventDispatcher.cs index e9341ca80..e1b9ccf94 100644 --- a/src/Squidex.Domain.Apps.Events/Schemas/Utils/SchemaEventDispatcher.cs +++ b/src/Squidex.Domain.Apps.Events/Schemas/Utils/SchemaEventDispatcher.cs @@ -9,7 +9,6 @@ using System; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Infrastructure.Reflection; namespace Squidex.Domain.Apps.Events.Schemas.Utils { @@ -61,7 +60,7 @@ namespace Squidex.Domain.Apps.Events.Schemas.Utils return schema; } - public static void Apply(this Schema schema, FieldAdded @event, FieldRegistry registry) + public static Schema Apply(this Schema schema, FieldAdded @event, FieldRegistry registry) { var partitioning = string.Equals(@event.Partitioning, Partitioning.Language.Key, StringComparison.OrdinalIgnoreCase) ? @@ -71,86 +70,65 @@ namespace Squidex.Domain.Apps.Events.Schemas.Utils var fieldId = @event.FieldId.Id; var field = registry.CreateField(fieldId, @event.Name, partitioning, @event.Properties); - schema.DeleteField(fieldId); - schema.AddField(field); - } + schema = schema.DeleteField(fieldId); + schema = schema.AddField(field); - public static void Apply(this Schema schema, FieldUpdated @event) - { - if (schema.FieldsById.TryGetValue(@event.FieldId.Id, out var field)) - { - field.Update(@event.Properties); - } + return schema; } - public static void Apply(this Schema schema, FieldLocked @event) + public static Schema Apply(this Schema schema, FieldUpdated @event) { - if (schema.FieldsById.TryGetValue(@event.FieldId.Id, out var field)) - { - field.Lock(); - } + return schema.UpdateField(@event.FieldId.Id, @event.Properties); } - public static void Apply(this Schema schema, FieldHidden @event) + public static Schema Apply(this Schema schema, FieldLocked @event) { - if (schema.FieldsById.TryGetValue(@event.FieldId.Id, out var field)) - { - field.Hide(); - } + return schema.LockField(@event.FieldId.Id); } - public static void Apply(this Schema schema, FieldShown @event) + public static Schema Apply(this Schema schema, FieldHidden @event) { - if (schema.FieldsById.TryGetValue(@event.FieldId.Id, out var field)) - { - field.Show(); - } + return schema.HideField(@event.FieldId.Id); } - public static void Apply(this Schema schema, FieldDisabled @event) + public static Schema Apply(this Schema schema, FieldShown @event) { - if (schema.FieldsById.TryGetValue(@event.FieldId.Id, out var field)) - { - field.Disable(); - } + return schema.ShowField(@event.FieldId.Id); } - public static void Apply(this Schema schema, FieldEnabled @event) + public static Schema Apply(this Schema schema, FieldDisabled @event) { - if (schema.FieldsById.TryGetValue(@event.FieldId.Id, out var field)) - { - field.Enable(); - } + return schema.DisableField(@event.FieldId.Id); } - public static void Apply(this Schema schema, SchemaUpdated @event) + public static Schema Apply(this Schema schema, FieldEnabled @event) { - schema.Update(@event.Properties); + return schema.EnableField(@event.FieldId.Id); } - public static void Apply(this Schema schema, SchemaFieldsReordered @event) + public static Schema Apply(this Schema schema, SchemaUpdated @event) { - schema.ReorderFields(@event.FieldIds); + return schema.Update(@event.Properties); } - public static void Apply(this Schema schema, FieldDeleted @event) + public static Schema Apply(this Schema schema, SchemaFieldsReordered @event) { - schema.DeleteField(@event.FieldId.Id); + return schema.ReorderFields(@event.FieldIds); } - public static void Apply(this Schema schema, SchemaPublished @event) + public static Schema Apply(this Schema schema, FieldDeleted @event) { - schema.Publish(); + return schema.DeleteField(@event.FieldId.Id); } - public static void Apply(this Schema schema, SchemaUnpublished @event) + public static Schema Apply(this Schema schema, SchemaPublished @event) { - schema.Unpublish(); + return schema.Publish(); } - public static void Apply(this Schema schema, ScriptsConfigured @event) + public static Schema Apply(this Schema schema, SchemaUnpublished @event) { - SimpleMapper.Map(@event, schema); + return schema.Unpublish(); } } } diff --git a/src/Squidex.Domain.Apps.Read/Assets/IAssetEntity.cs b/src/Squidex.Domain.Apps.Read/Assets/IAssetEntity.cs index 46b0e4ea9..ae175abb6 100644 --- a/src/Squidex.Domain.Apps.Read/Assets/IAssetEntity.cs +++ b/src/Squidex.Domain.Apps.Read/Assets/IAssetEntity.cs @@ -8,7 +8,7 @@ namespace Squidex.Domain.Apps.Read.Assets { - public interface IAssetEntity : IAppRefEntity, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion + public interface IAssetEntity : IEntityWithAppRef, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion { string MimeType { get; } diff --git a/src/Squidex.Domain.Apps.Read/Contents/IContentEntity.cs b/src/Squidex.Domain.Apps.Read/Contents/IContentEntity.cs index dc34ea65e..fb704d635 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/IContentEntity.cs +++ b/src/Squidex.Domain.Apps.Read/Contents/IContentEntity.cs @@ -11,7 +11,7 @@ using Squidex.Domain.Apps.Core.Contents; namespace Squidex.Domain.Apps.Read.Contents { - public interface IContentEntity : IAppRefEntity, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion + public interface IContentEntity : IEntityWithAppRef, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion { Status Status { get; } diff --git a/src/Squidex.Domain.Apps.Read/EntityMapper.cs b/src/Squidex.Domain.Apps.Read/EntityMapper.cs index 2efae1cfd..525f94ff2 100644 --- a/src/Squidex.Domain.Apps.Read/EntityMapper.cs +++ b/src/Squidex.Domain.Apps.Read/EntityMapper.cs @@ -57,7 +57,7 @@ namespace Squidex.Domain.Apps.Read private static void SetVersion(EnvelopeHeaders headers, IEntity entity) { - if (entity is IEntityWithVersion withVersion) + if (entity is IUpdateableEntityWithVersion withVersion) { withVersion.Version = headers.EventStreamNumber(); } @@ -65,7 +65,7 @@ namespace Squidex.Domain.Apps.Read private static void SetCreatedBy(SquidexEvent @event, IEntity entity) { - if (entity is IEntityWithCreatedBy withCreatedBy) + if (entity is IUpdateableEntityWithCreatedBy withCreatedBy) { withCreatedBy.CreatedBy = @event.Actor; } @@ -73,7 +73,7 @@ namespace Squidex.Domain.Apps.Read private static void SetLastModifiedBy(SquidexEvent @event, IEntity entity) { - if (entity is IEntityWithLastModifiedBy withModifiedBy) + if (entity is IUpdateableEntityWithLastModifiedBy withModifiedBy) { withModifiedBy.LastModifiedBy = @event.Actor; } @@ -81,7 +81,7 @@ namespace Squidex.Domain.Apps.Read private static void SetAppId(SquidexEvent @event, IEntity entity) { - if (entity is IAppRefEntity app && @event is AppEvent appEvent) + if (entity is IUpdateableEntityWithAppRef app && @event is AppEvent appEvent) { app.AppId = appEvent.AppId.Id; } diff --git a/src/Squidex.Domain.Apps.Read/IEntityWithAppRef.cs b/src/Squidex.Domain.Apps.Read/IEntityWithAppRef.cs new file mode 100644 index 000000000..1070538d1 --- /dev/null +++ b/src/Squidex.Domain.Apps.Read/IEntityWithAppRef.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// IEntityWithAppRef.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace Squidex.Domain.Apps.Read +{ + public interface IEntityWithAppRef : IEntity + { + Guid AppId { get; } + } +} \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Read/IEntityWithCreatedBy.cs b/src/Squidex.Domain.Apps.Read/IEntityWithCreatedBy.cs index e06701152..4661a15ee 100644 --- a/src/Squidex.Domain.Apps.Read/IEntityWithCreatedBy.cs +++ b/src/Squidex.Domain.Apps.Read/IEntityWithCreatedBy.cs @@ -12,6 +12,6 @@ namespace Squidex.Domain.Apps.Read { public interface IEntityWithCreatedBy { - RefToken CreatedBy { get; set; } + RefToken CreatedBy { get; } } } diff --git a/src/Squidex.Domain.Apps.Read/IEntityWithVersion.cs b/src/Squidex.Domain.Apps.Read/IEntityWithVersion.cs index b80ed6b08..860918deb 100644 --- a/src/Squidex.Domain.Apps.Read/IEntityWithVersion.cs +++ b/src/Squidex.Domain.Apps.Read/IEntityWithVersion.cs @@ -10,6 +10,6 @@ namespace Squidex.Domain.Apps.Read { public interface IEntityWithVersion { - long Version { get; set; } + long Version { get; } } } diff --git a/src/Squidex.Domain.Apps.Read/IAppRefEntity.cs b/src/Squidex.Domain.Apps.Read/IUpdateableEntityWithAppRef.cs similarity index 82% rename from src/Squidex.Domain.Apps.Read/IAppRefEntity.cs rename to src/Squidex.Domain.Apps.Read/IUpdateableEntityWithAppRef.cs index f92f52ffc..316962a96 100644 --- a/src/Squidex.Domain.Apps.Read/IAppRefEntity.cs +++ b/src/Squidex.Domain.Apps.Read/IUpdateableEntityWithAppRef.cs @@ -1,5 +1,5 @@ // ========================================================================== -// IAppRefEntity.cs +// IUpdateableEntityWithAppRef.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -10,8 +10,8 @@ using System; namespace Squidex.Domain.Apps.Read { - public interface IAppRefEntity : IEntity + public interface IUpdateableEntityWithAppRef { Guid AppId { get; set; } } -} \ No newline at end of file +} diff --git a/src/Squidex.Domain.Apps.Read/IUpdateableEntityWithCreatedBy.cs b/src/Squidex.Domain.Apps.Read/IUpdateableEntityWithCreatedBy.cs new file mode 100644 index 000000000..3675937c4 --- /dev/null +++ b/src/Squidex.Domain.Apps.Read/IUpdateableEntityWithCreatedBy.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// IUpdateableEntityWithCreatedBy.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Read +{ + public interface IUpdateableEntityWithCreatedBy + { + RefToken CreatedBy { get; set; } + } +} diff --git a/src/Squidex.Domain.Apps.Read/IUpdateableEntityWithLastModifiedBy.cs b/src/Squidex.Domain.Apps.Read/IUpdateableEntityWithLastModifiedBy.cs new file mode 100644 index 000000000..d1aedc9f4 --- /dev/null +++ b/src/Squidex.Domain.Apps.Read/IUpdateableEntityWithLastModifiedBy.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// IUpdateableEntityWithLastModifiedBy.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Read +{ + public interface IUpdateableEntityWithLastModifiedBy + { + RefToken LastModifiedBy { get; set; } + } +} diff --git a/src/Squidex.Infrastructure/Json/Orleans/IJsonValue.cs b/src/Squidex.Domain.Apps.Read/IUpdateableEntityWithVersion.cs similarity index 67% rename from src/Squidex.Infrastructure/Json/Orleans/IJsonValue.cs rename to src/Squidex.Domain.Apps.Read/IUpdateableEntityWithVersion.cs index f2d8ab41f..8d08b4c6a 100644 --- a/src/Squidex.Infrastructure/Json/Orleans/IJsonValue.cs +++ b/src/Squidex.Domain.Apps.Read/IUpdateableEntityWithVersion.cs @@ -1,17 +1,15 @@ // ========================================================================== -// IJsonValue.cs +// IUpdateableEntityWithVersion.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== -namespace Squidex.Infrastructure.Json.Orleans +namespace Squidex.Domain.Apps.Read { - public interface IJsonValue + public interface IUpdateableEntityWithVersion { - object Value { get; } - - bool IsImmutable { get; } + long Version { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Read/Rules/IRuleEntity.cs b/src/Squidex.Domain.Apps.Read/Rules/IRuleEntity.cs index 6de2db9dd..424febe38 100644 --- a/src/Squidex.Domain.Apps.Read/Rules/IRuleEntity.cs +++ b/src/Squidex.Domain.Apps.Read/Rules/IRuleEntity.cs @@ -10,8 +10,8 @@ using Squidex.Domain.Apps.Core.Rules; namespace Squidex.Domain.Apps.Read.Rules { - public interface IRuleEntity : IAppRefEntity, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion + public interface IRuleEntity : IEntityWithAppRef, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion { - Rule Rule { get; } + Rule RuleDef { get; } } } diff --git a/src/Squidex.Domain.Apps.Read/Rules/RuleEnqueuer.cs b/src/Squidex.Domain.Apps.Read/Rules/RuleEnqueuer.cs index 5629f61e7..5a3a492d4 100644 --- a/src/Squidex.Domain.Apps.Read/Rules/RuleEnqueuer.cs +++ b/src/Squidex.Domain.Apps.Read/Rules/RuleEnqueuer.cs @@ -60,7 +60,7 @@ namespace Squidex.Domain.Apps.Read.Rules foreach (var ruleEntity in rules) { - var job = ruleService.CreateJob(ruleEntity.Rule, @event); + var job = ruleService.CreateJob(ruleEntity.RuleDef, @event); if (job != null) { diff --git a/src/Squidex.Domain.Apps.Read/Schemas/ISchemaEntity.cs b/src/Squidex.Domain.Apps.Read/Schemas/ISchemaEntity.cs index 17348a1b4..90caafd74 100644 --- a/src/Squidex.Domain.Apps.Read/Schemas/ISchemaEntity.cs +++ b/src/Squidex.Domain.Apps.Read/Schemas/ISchemaEntity.cs @@ -10,7 +10,7 @@ using Squidex.Domain.Apps.Core.Schemas; namespace Squidex.Domain.Apps.Read.Schemas { - public interface ISchemaEntity : IAppRefEntity, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion + public interface ISchemaEntity : IEntityWithAppRef, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion { string Name { get; } diff --git a/src/Squidex.Domain.Apps.Read/State/Orleans/AppStateEventConsumer.cs b/src/Squidex.Domain.Apps.Read/State/Orleans/AppStateEventConsumer.cs index 9ca3a6203..2b136236e 100644 --- a/src/Squidex.Domain.Apps.Read/State/Orleans/AppStateEventConsumer.cs +++ b/src/Squidex.Domain.Apps.Read/State/Orleans/AppStateEventConsumer.cs @@ -8,6 +8,7 @@ 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; @@ -49,7 +50,7 @@ namespace Squidex.Domain.Apps.Read.State.Orleans { var appGrain = factory.GetGrain(appEvent.AppId.Name); - await appGrain.HandleAsync(@event); + await appGrain.HandleAsync(new Immutable>(@event)); } 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 e3bfe27ee..d2d3676dc 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(J> message); + Task HandleAsync(Immutable> 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 ba964bfbb..3310a0b0c 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 @@ -10,13 +10,13 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Orleans; +using Orleans.Concurrency; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Read.Apps; 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; namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations { @@ -31,51 +31,51 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations this.fieldRegistry = fieldRegistry; } - public Task> GetAppWithSchemaAsync(Guid id) + public Task> GetAppWithSchemaAsync(Guid id) { var schema = State.FindSchema(x => x.Id == id && !x.IsDeleted); - return J<(IAppEntity AppEntity, ISchemaEntity SchemaEntity)>.AsTask((State.GetApp(), schema)); + return Task.FromResult((State.GetApp(), schema).AsImmutable()); } - public Task> GetAppAsync() + public Task> GetAppAsync() { var value = State.GetApp(); - return J.AsTask(value); + return Task.FromResult(value.AsImmutable()); } - public Task>> GetRulesAsync() + public Task>> GetRulesAsync() { var value = State.FindRules(); - return J>.AsTask(value); + return Task.FromResult(value.AsImmutable()); } - public Task>> GetSchemasAsync() + public Task>> GetSchemasAsync() { var value = State.FindSchemas(x => !x.IsDeleted); - return J>.AsTask(value); + return Task.FromResult(value.AsImmutable()); } - 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 J.AsTask(value); + return Task.FromResult(value.AsImmutable()); } - 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 J.AsTask(value); + return Task.FromResult(value.AsImmutable()); } - public Task HandleAsync(J> message) + public Task HandleAsync(Immutable> message) { - State.Apply(message, fieldRegistry); + State.Apply(message.Value, fieldRegistry); return WriteStateAsync(); } diff --git a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrainState.cs b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrainState.cs index 458dce6cd..d8beec0a2 100644 --- a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrainState.cs +++ b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/AppStateGrainState.cs @@ -163,21 +163,21 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations case RuleCreated @event: Rules[@event.RuleId] = EntityMapper.Create(@event, envelope.Headers, r => { - r.Rule = RuleEventDispatcher.Create(@event); + r.RuleDef = RuleEventDispatcher.Create(@event); }); break; case RuleUpdated @event: UpdateRule(envelope, r => { - r.Rule.Apply(@event); + r.RuleDef = r.RuleDef.Apply(@event); }); break; case RuleEnabled @event: UpdateRule(envelope, r => { - r.Rule.Apply(@event); + r.RuleDef = r.RuleDef.Apply(@event); }); break; @@ -197,84 +197,84 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations case FieldAdded @event: UpdateSchema(envelope, s => { - s.SchemaDef.Apply(@event, registry); + s.SchemaDef = s.SchemaDef.Apply(@event, registry); }); break; case FieldDeleted @event: UpdateSchema(envelope, s => { - s.SchemaDef.Apply(@event); + s.SchemaDef = s.SchemaDef.Apply(@event); }); break; case FieldLocked @event: UpdateSchema(envelope, s => { - s.SchemaDef.Apply(@event); + s.SchemaDef = s.SchemaDef.Apply(@event); }); break; case FieldHidden @event: UpdateSchema(envelope, s => { - s.SchemaDef.Apply(@event); + s.SchemaDef = s.SchemaDef.Apply(@event); }); break; case FieldShown @event: UpdateSchema(envelope, s => { - s.SchemaDef.Apply(@event); + s.SchemaDef = s.SchemaDef.Apply(@event); }); break; case FieldDisabled @event: UpdateSchema(envelope, s => { - s.SchemaDef.Apply(@event); + s.SchemaDef = s.SchemaDef.Apply(@event); }); break; case FieldEnabled @event: UpdateSchema(envelope, s => { - s.SchemaDef.Apply(@event); + s.SchemaDef = s.SchemaDef.Apply(@event); }); break; case FieldUpdated @event: UpdateSchema(envelope, s => { - s.SchemaDef.Apply(@event); + s.SchemaDef = s.SchemaDef.Apply(@event); }); break; case SchemaFieldsReordered @event: UpdateSchema(envelope, s => { - s.SchemaDef.Apply(@event); + s.SchemaDef = s.SchemaDef.Apply(@event); }); break; case SchemaUpdated @event: UpdateSchema(envelope, s => { - s.SchemaDef.Apply(@event); + s.SchemaDef = s.SchemaDef.Apply(@event); }); break; case SchemaPublished @event: UpdateSchema(envelope, s => { - s.SchemaDef.Apply(@event); + s.SchemaDef = s.SchemaDef.Apply(@event); }); break; case ScriptsConfigured @event: UpdateSchema(envelope, s => { - s.SchemaDef.Apply(@event); + SimpleMapper.Map(s, @event); }); break; @@ -308,14 +308,14 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations { var e = envelope.To(); - Rules[e.Payload.RuleId].Update(e.Payload, e.Headers, updater); + Rules[e.Payload.RuleId].Clone().Update(e.Payload, e.Headers, updater); } private void UpdateSchema(Envelope envelope, Action updater = null) { var e = envelope.To(); - Schemas[e.Payload.SchemaId.Id].Copy().Update(e.Payload, e.Headers, updater); + Schemas[e.Payload.SchemaId.Id].Clone().Update(e.Payload, e.Headers, updater); } } } \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonAppEntity.cs b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonAppEntity.cs index 6dd326d57..2d2ce4ec6 100644 --- a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonAppEntity.cs +++ b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonAppEntity.cs @@ -7,12 +7,14 @@ // ========================================================================== using Newtonsoft.Json; +using Orleans.Concurrency; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Read.Apps; namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations { - public sealed class JsonAppEntity : JsonEntity, IAppEntity + [Immutable] + public sealed class JsonAppEntity : JsonEntity, IAppEntity { [JsonProperty] public string Name { get; set; } diff --git a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonEntity.cs b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonEntity.cs index 90ab8aa02..6da734b18 100644 --- a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonEntity.cs +++ b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonEntity.cs @@ -9,10 +9,13 @@ using System; using Newtonsoft.Json; using NodaTime; +using Orleans.Concurrency; +using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations { - public abstract class JsonEntity + [Immutable] + public abstract class JsonEntity : Cloneable, IUpdateableEntityWithVersion where T : Cloneable { [JsonProperty] public Guid Id { get; set; } @@ -25,5 +28,10 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations [JsonProperty] public long Version { get; set; } + + public T Clone() + { + return Clone(x => { }); + } } } diff --git a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonRuleEntity.cs b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonRuleEntity.cs index 05e37c1fa..6916db43e 100644 --- a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonRuleEntity.cs +++ b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonRuleEntity.cs @@ -8,13 +8,20 @@ using System; using Newtonsoft.Json; +using Orleans.Concurrency; using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Read.Rules; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations { - public sealed class JsonRuleEntity : JsonEntity, IRuleEntity + [Immutable] + public sealed class JsonRuleEntity : + JsonEntity, + IRuleEntity, + IUpdateableEntityWithAppRef, + IUpdateableEntityWithCreatedBy, + IUpdateableEntityWithLastModifiedBy { [JsonProperty] public Guid AppId { get; set; } @@ -26,6 +33,6 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations public RefToken LastModifiedBy { get; set; } [JsonProperty] - public Rule Rule { get; set; } + public Rule RuleDef { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonSchemaEntity.cs b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonSchemaEntity.cs index d038aed0f..026ed1bd7 100644 --- a/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonSchemaEntity.cs +++ b/src/Squidex.Domain.Apps.Read/State/Orleans/Grains/Implementations/JsonSchemaEntity.cs @@ -8,13 +8,20 @@ using System; using Newtonsoft.Json; +using Orleans.Concurrency; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Read.Schemas; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Read.State.Orleans.Grains.Implementations { - public sealed class JsonSchemaEntity : JsonEntity, ISchemaEntity + [Immutable] + public sealed class JsonSchemaEntity : + JsonEntity, + ISchemaEntity, + IUpdateableEntityWithAppRef, + IUpdateableEntityWithCreatedBy, + IUpdateableEntityWithLastModifiedBy { [JsonProperty] public string Name { get; set; } diff --git a/src/Squidex.Infrastructure/Json/Orleans/J.cs b/src/Squidex.Infrastructure/Json/Orleans/J.cs deleted file mode 100644 index c137b6a8d..000000000 --- a/src/Squidex.Infrastructure/Json/Orleans/J.cs +++ /dev/null @@ -1,57 +0,0 @@ -// ========================================================================== -// 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/JsonExternalSerializer.cs b/src/Squidex.Infrastructure/Json/Orleans/JsonExternalSerializer.cs index 80566b15d..a6806add5 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,12 +19,15 @@ namespace Squidex.Infrastructure.Json.Orleans public class JsonExternalSerializer : IExternalSerializer { private readonly JsonSerializer serializer; + private readonly HashSet types; - public JsonExternalSerializer(JsonSerializer serializer) + public JsonExternalSerializer(JsonSerializer serializer, params Type[] types) { Guard.NotNull(serializer, nameof(serializer)); this.serializer = serializer; + + this.types = new HashSet(types); } public void Initialize(Logger logger) @@ -33,25 +36,15 @@ namespace Squidex.Infrastructure.Json.Orleans public bool IsSupportedType(Type itemType) { - return itemType.GetInterfaces().Contains(typeof(IJsonValue)); + return types.Contains(itemType); } public object DeepCopy(object source, ICopyContext context) { - var jsonValue = source as IJsonValue; - - if (jsonValue == null) + if (source == 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);