diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs index d491c5c12..a50f1196d 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs @@ -7,109 +7,81 @@ using System; using System.Threading.Tasks; +using Orleans; using Squidex.Domain.Apps.Entities.Assets.Commands; -using Squidex.Domain.Apps.Entities.Assets.Guards; using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Commands; -using Squidex.Infrastructure.Dispatching; namespace Squidex.Domain.Apps.Entities.Assets { - public class AssetCommandMiddleware : ICommandMiddleware + public sealed class AssetCommandMiddleware : GrainCommandMiddleware { - private readonly IAggregateHandler handler; private readonly IAssetStore assetStore; private readonly IAssetThumbnailGenerator assetThumbnailGenerator; public AssetCommandMiddleware( - IAggregateHandler handler, + IGrainFactory grainFactory, IAssetStore assetStore, IAssetThumbnailGenerator assetThumbnailGenerator) + : base(grainFactory) { - Guard.NotNull(handler, nameof(handler)); Guard.NotNull(assetStore, nameof(assetStore)); Guard.NotNull(assetThumbnailGenerator, nameof(assetThumbnailGenerator)); - this.handler = handler; this.assetStore = assetStore; this.assetThumbnailGenerator = assetThumbnailGenerator; } - protected async Task On(CreateAsset command, CommandContext context) + public async override Task HandleAsync(CommandContext context, Func next) { - command.ImageInfo = await assetThumbnailGenerator.GetImageInfoAsync(command.File.OpenRead()); - try + switch (context.Command) { - var asset = await handler.CreateSyncedAsync(context, async a => - { - GuardAsset.CanCreate(command); - - a.Create(command); - - await assetStore.UploadTemporaryAsync(context.ContextId.ToString(), command.File.OpenRead()); - - context.Complete(EntityCreatedResult.Create(command.AssetId, a.Version)); - }); - - await assetStore.CopyTemporaryAsync(context.ContextId.ToString(), command.AssetId.ToString(), asset.Snapshot.FileVersion, null); - } - finally - { - await assetStore.DeleteTemporaryAsync(context.ContextId.ToString()); - } - } - - protected async Task On(UpdateAsset command, CommandContext context) - { - command.ImageInfo = await assetThumbnailGenerator.GetImageInfoAsync(command.File.OpenRead()); - - try - { - var asset = await handler.UpdateSyncedAsync(context, async a => - { - GuardAsset.CanUpdate(command); - - a.Update(command); - - await assetStore.UploadTemporaryAsync(context.ContextId.ToString(), command.File.OpenRead()); - - context.Complete(new AssetSavedResult(a.Version, a.Snapshot.FileVersion)); - }); - - await assetStore.CopyTemporaryAsync(context.ContextId.ToString(), command.AssetId.ToString(), asset.Snapshot.FileVersion, null); - } - finally - { - await assetStore.DeleteTemporaryAsync(context.ContextId.ToString()); - } - } - - protected Task On(RenameAsset command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, a => - { - GuardAsset.CanRename(command, a.Snapshot.FileName); - - a.Rename(command); - }); - } - - protected Task On(DeleteAsset command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, a => - { - GuardAsset.CanDelete(command); - - a.Delete(command); - }); - } - - public async Task HandleAsync(CommandContext context, Func next) - { - if (!await this.DispatchActionAsync(context.Command, context)) - { - await next(); + case CreateAsset createAsset: + { + createAsset.ImageInfo = await assetThumbnailGenerator.GetImageInfoAsync(createAsset.File.OpenRead()); + + await assetStore.UploadTemporaryAsync(context.ContextId.ToString(), createAsset.File.OpenRead()); + try + { + var result = await ExecuteCommandAsync(createAsset) as AssetSavedResult; + + context.Complete(EntityCreatedResult.Create(createAsset.AssetId, result.Version)); + + await assetStore.CopyTemporaryAsync(context.ContextId.ToString(), createAsset.AssetId.ToString(), result.FileVersion, null); + } + finally + { + await assetStore.DeleteTemporaryAsync(context.ContextId.ToString()); + } + + break; + } + + case UpdateAsset updateAsset: + { + updateAsset.ImageInfo = await assetThumbnailGenerator.GetImageInfoAsync(updateAsset.File.OpenRead()); + + await assetStore.UploadTemporaryAsync(context.ContextId.ToString(), updateAsset.File.OpenRead()); + try + { + var result = await ExecuteCommandAsync(updateAsset) as AssetSavedResult; + + context.Complete(result); + + await assetStore.CopyTemporaryAsync(context.ContextId.ToString(), updateAsset.AssetId.ToString(), result.FileVersion, null); + } + finally + { + await assetStore.DeleteTemporaryAsync(context.ContextId.ToString()); + } + + break; + } + + default: + await base.HandleAsync(context, next); + break; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs similarity index 54% rename from src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs rename to src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs index 7cc351c83..1aa3bfdc3 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs @@ -5,22 +5,71 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; +using System.Threading.Tasks; using Squidex.Domain.Apps.Entities.Assets.Commands; +using Squidex.Domain.Apps.Entities.Assets.Guards; using Squidex.Domain.Apps.Entities.Assets.State; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Assets; using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; +using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.Assets { - public sealed class AssetDomainObject : SquidexDomainObjectBase + public sealed class AssetGrain : DomainObjectGrain, IAssetGrain { - public AssetDomainObject Create(CreateAsset command) + public AssetGrain(IStore store) + : base(store) { - VerifyNotCreated(); + } + + public override Task ExecuteAsync(IAggregateCommand command) + { + switch (command) + { + case CreateAsset createRule: + return CreateReturnAsync(createRule, c => + { + GuardAsset.CanCreate(c); + + Create(c); + + return new AssetSavedResult(NewVersion, Snapshot.FileVersion); + }); + case UpdateAsset updateRule: + return UpdateReturnAsync(updateRule, c => + { + GuardAsset.CanUpdate(c); + + Update(c); + + return new AssetSavedResult(NewVersion, Snapshot.FileVersion); + }); + case RenameAsset renameAsset: + return UpdateAsync(renameAsset, c => + { + GuardAsset.CanRename(c, Snapshot.FileName); + + Rename(c); + }); + case DeleteAsset deleteAsset: + return UpdateAsync(deleteAsset, c => + { + GuardAsset.CanDelete(c); + + Delete(c); + }); + default: + throw new NotSupportedException(); + } + } + public void Create(CreateAsset command) + { var @event = SimpleMapper.Map(command, new AssetCreated { FileName = command.File.FileName, @@ -33,13 +82,11 @@ namespace Squidex.Domain.Apps.Entities.Assets }); RaiseEvent(@event); - - return this; } - public AssetDomainObject Update(UpdateAsset command) + public void Update(UpdateAsset command) { - VerifyCreatedAndNotDeleted(); + VerifyNotDeleted(); var @event = SimpleMapper.Map(command, new AssetUpdated { @@ -52,26 +99,20 @@ namespace Squidex.Domain.Apps.Entities.Assets }); RaiseEvent(@event); - - return this; } - public AssetDomainObject Delete(DeleteAsset command) + public void Delete(DeleteAsset command) { - VerifyCreatedAndNotDeleted(); + VerifyNotDeleted(); RaiseEvent(SimpleMapper.Map(command, new AssetDeleted { DeletedSize = Snapshot.TotalSize })); - - return this; } - public AssetDomainObject Rename(RenameAsset command) + public void Rename(RenameAsset command) { - VerifyCreatedAndNotDeleted(); + VerifyNotDeleted(); RaiseEvent(SimpleMapper.Map(command, new AssetRenamed())); - - return this; } private void RaiseEvent(AppEvent @event) @@ -84,19 +125,11 @@ namespace Squidex.Domain.Apps.Entities.Assets RaiseEvent(Envelope.Create(@event)); } - private void VerifyNotCreated() - { - if (!string.IsNullOrWhiteSpace(Snapshot.FileName)) - { - throw new DomainException("Asset has already been created."); - } - } - - private void VerifyCreatedAndNotDeleted() + private void VerifyNotDeleted() { - if (Snapshot.IsDeleted || string.IsNullOrWhiteSpace(Snapshot.FileName)) + if (Snapshot.IsDeleted) { - throw new DomainException("Asset has already been deleted or not created yet."); + throw new DomainException("Asset has already been deleted"); } } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/IAssetGrain.cs b/src/Squidex.Domain.Apps.Entities/Assets/IAssetGrain.cs new file mode 100644 index 000000000..864c0256a --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Assets/IAssetGrain.cs @@ -0,0 +1,15 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Infrastructure.Commands; + +namespace Squidex.Domain.Apps.Entities.Assets +{ + public interface IAssetGrain : IDomainObjectGrain + { + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Rules/IRuleGrain.cs b/src/Squidex.Domain.Apps.Entities/Rules/IRuleGrain.cs new file mode 100644 index 000000000..7bab17d69 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Rules/IRuleGrain.cs @@ -0,0 +1,15 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Infrastructure.Commands; + +namespace Squidex.Domain.Apps.Entities.Rules +{ + public interface IRuleGrain : IDomainObjectGrain + { + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs deleted file mode 100644 index 8a6e43441..000000000 --- a/src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs +++ /dev/null @@ -1,89 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Threading.Tasks; -using Squidex.Domain.Apps.Entities.Rules.Commands; -using Squidex.Domain.Apps.Entities.Rules.Guards; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; -using Squidex.Infrastructure.Dispatching; - -namespace Squidex.Domain.Apps.Entities.Rules -{ - public class RuleCommandMiddleware : ICommandMiddleware - { - private readonly IAggregateHandler handler; - private readonly IAppProvider appProvider; - - public RuleCommandMiddleware(IAggregateHandler handler, IAppProvider appProvider) - { - Guard.NotNull(handler, nameof(handler)); - Guard.NotNull(appProvider, nameof(appProvider)); - - this.handler = handler; - - this.appProvider = appProvider; - } - - protected Task On(CreateRule command, CommandContext context) - { - return handler.CreateSyncedAsync(context, async r => - { - await GuardRule.CanCreate(command, appProvider); - - r.Create(command); - }); - } - - protected Task On(UpdateRule command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, async r => - { - await GuardRule.CanUpdate(command, r.Snapshot.AppId.Id, appProvider); - - r.Update(command); - }); - } - - protected Task On(EnableRule command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, r => - { - GuardRule.CanEnable(command, r.Snapshot.RuleDef); - - r.Enable(command); - }); - } - - protected Task On(DisableRule command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, r => - { - GuardRule.CanDisable(command, r.Snapshot.RuleDef); - - r.Disable(command); - }); - } - - protected Task On(DeleteRule command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, r => - { - GuardRule.CanDelete(command); - - r.Delete(command); - }); - } - - public async Task HandleAsync(CommandContext context, Func next) - { - await this.DispatchActionAsync(context.Command, context); - await next(); - } - } -} diff --git a/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs deleted file mode 100644 index 54c9afc2d..000000000 --- a/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs +++ /dev/null @@ -1,86 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Entities.Rules.Commands; -using Squidex.Domain.Apps.Entities.Rules.State; -using Squidex.Domain.Apps.Events; -using Squidex.Domain.Apps.Events.Rules; -using Squidex.Infrastructure; -using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.Reflection; - -namespace Squidex.Domain.Apps.Entities.Rules -{ - public sealed class RuleDomainObject : SquidexDomainObjectBase - { - public void Create(CreateRule command) - { - VerifyNotCreated(); - - RaiseEvent(SimpleMapper.Map(command, new RuleCreated())); - } - - public void Update(UpdateRule command) - { - VerifyCreatedAndNotDeleted(); - - RaiseEvent(SimpleMapper.Map(command, new RuleUpdated())); - } - - public void Enable(EnableRule command) - { - VerifyCreatedAndNotDeleted(); - - RaiseEvent(SimpleMapper.Map(command, new RuleEnabled())); - } - - public void Disable(DisableRule command) - { - VerifyCreatedAndNotDeleted(); - - RaiseEvent(SimpleMapper.Map(command, new RuleDisabled())); - } - - public void Delete(DeleteRule command) - { - VerifyCreatedAndNotDeleted(); - - RaiseEvent(SimpleMapper.Map(command, new RuleDeleted())); - } - - private void RaiseEvent(AppEvent @event) - { - if (@event.AppId == null) - { - @event.AppId = Snapshot.AppId; - } - - RaiseEvent(Envelope.Create(@event)); - } - - private void VerifyNotCreated() - { - if (Snapshot.RuleDef != null) - { - throw new DomainException("Webhook has already been created."); - } - } - - private void VerifyCreatedAndNotDeleted() - { - if (Snapshot.IsDeleted || Snapshot.RuleDef == null) - { - throw new DomainException("Webhook has already been deleted or not created yet."); - } - } - - public override void ApplyEvent(Envelope @event) - { - ApplySnapshot(Snapshot.Apply(@event)); - } - } -} diff --git a/src/Squidex.Domain.Apps.Entities/Rules/RuleGrain.cs b/src/Squidex.Domain.Apps.Entities/Rules/RuleGrain.cs new file mode 100644 index 000000000..668296c4e --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Rules/RuleGrain.cs @@ -0,0 +1,135 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using Squidex.Domain.Apps.Entities.Rules.Commands; +using Squidex.Domain.Apps.Entities.Rules.Guards; +using Squidex.Domain.Apps.Entities.Rules.State; +using Squidex.Domain.Apps.Events; +using Squidex.Domain.Apps.Events.Rules; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Reflection; +using Squidex.Infrastructure.States; + +namespace Squidex.Domain.Apps.Entities.Rules +{ + public sealed class RuleGrain : DomainObjectGrain, IRuleGrain + { + private readonly IAppProvider appProvider; + + public RuleGrain(IStore store, IAppProvider appProvider) + : base(store) + { + Guard.NotNull(appProvider, nameof(appProvider)); + + this.appProvider = appProvider; + } + + public override Task ExecuteAsync(IAggregateCommand command) + { + switch (command) + { + case CreateRule createRule: + return CreateAsync(createRule, c => + { + GuardRule.CanCreate(c, appProvider); + + Create(c); + }); + case UpdateRule updateRule: + return UpdateAsync(updateRule, c => + { + GuardRule.CanUpdate(c, Snapshot.AppId.Id, appProvider); + + Update(c); + }); + case EnableRule enableRule: + return UpdateAsync(enableRule, c => + { + GuardRule.CanEnable(c, Snapshot.RuleDef); + + Enable(c); + }); + case DisableRule disableRule: + return UpdateAsync(disableRule, c => + { + GuardRule.CanDisable(c, Snapshot.RuleDef); + + Disable(c); + }); + case DeleteRule deleteRule: + return UpdateAsync(deleteRule, c => + { + GuardRule.CanDelete(deleteRule); + + Delete(c); + }); + default: + throw new NotSupportedException(); + } + } + + public void Create(CreateRule command) + { + RaiseEvent(SimpleMapper.Map(command, new RuleCreated())); + } + + public void Update(UpdateRule command) + { + VerifyNotDeleted(); + + RaiseEvent(SimpleMapper.Map(command, new RuleUpdated())); + } + + public void Enable(EnableRule command) + { + VerifyNotDeleted(); + + RaiseEvent(SimpleMapper.Map(command, new RuleEnabled())); + } + + public void Disable(DisableRule command) + { + VerifyNotDeleted(); + + RaiseEvent(SimpleMapper.Map(command, new RuleDisabled())); + } + + public void Delete(DeleteRule command) + { + VerifyNotDeleted(); + + RaiseEvent(SimpleMapper.Map(command, new RuleDeleted())); + } + + private void RaiseEvent(AppEvent @event) + { + if (@event.AppId == null) + { + @event.AppId = Snapshot.AppId; + } + + RaiseEvent(Envelope.Create(@event)); + } + + private void VerifyNotDeleted() + { + if (Snapshot.IsDeleted || Snapshot.RuleDef == null) + { + throw new DomainException("Webhook has already been deleted."); + } + } + + public override void ApplyEvent(Envelope @event) + { + ApplySnapshot(Snapshot.Apply(@event)); + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaGrain.cs b/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaGrain.cs new file mode 100644 index 000000000..1f3b46dbf --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaGrain.cs @@ -0,0 +1,15 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Infrastructure.Commands; + +namespace Squidex.Domain.Apps.Entities.Schemas +{ + public interface ISchemaGrain : IDomainObjectGrain + { + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaCommandMiddleware.cs deleted file mode 100644 index b58f2c815..000000000 --- a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaCommandMiddleware.cs +++ /dev/null @@ -1,194 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Linq; -using System.Threading.Tasks; -using Squidex.Domain.Apps.Entities.Schemas.Commands; -using Squidex.Domain.Apps.Entities.Schemas.Guards; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; -using Squidex.Infrastructure.Dispatching; - -namespace Squidex.Domain.Apps.Entities.Schemas -{ - public class SchemaCommandMiddleware : ICommandMiddleware - { - private readonly IAppProvider appProvider; - private readonly IAggregateHandler handler; - - public SchemaCommandMiddleware(IAggregateHandler handler, IAppProvider appProvider) - { - Guard.NotNull(handler, nameof(handler)); - Guard.NotNull(appProvider, nameof(appProvider)); - - this.handler = handler; - - this.appProvider = appProvider; - } - - protected Task On(CreateSchema command, CommandContext context) - { - return handler.CreateSyncedAsync(context, async s => - { - await GuardSchema.CanCreate(command, appProvider); - - s.Create(command); - - context.Complete(EntityCreatedResult.Create(command.SchemaId, s.Version)); - }); - } - - protected Task On(AddField command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, s => - { - GuardSchemaField.CanAdd(s.Snapshot.SchemaDef, command); - - s.Add(command); - - context.Complete(EntityCreatedResult.Create(s.Snapshot.SchemaDef.FieldsById.Values.First(x => x.Name == command.Name).Id, s.Version)); - }); - } - - protected Task On(DeleteField command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, s => - { - GuardSchemaField.CanDelete(s.Snapshot.SchemaDef, command); - - s.DeleteField(command); - }); - } - - protected Task On(LockField command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, s => - { - GuardSchemaField.CanLock(s.Snapshot.SchemaDef, command); - - s.LockField(command); - }); - } - - protected Task On(HideField command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, s => - { - GuardSchemaField.CanHide(s.Snapshot.SchemaDef, command); - - s.HideField(command); - }); - } - - protected Task On(ShowField command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, s => - { - GuardSchemaField.CanShow(s.Snapshot.SchemaDef, command); - - s.ShowField(command); - }); - } - - protected Task On(DisableField command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, s => - { - GuardSchemaField.CanDisable(s.Snapshot.SchemaDef, command); - - s.DisableField(command); - }); - } - - protected Task On(EnableField command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, s => - { - GuardSchemaField.CanEnable(s.Snapshot.SchemaDef, command); - - s.EnableField(command); - }); - } - - protected Task On(UpdateField command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, s => - { - GuardSchemaField.CanUpdate(s.Snapshot.SchemaDef, command); - - s.UpdateField(command); - }); - } - - protected Task On(ReorderFields command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, s => - { - GuardSchema.CanReorder(s.Snapshot.SchemaDef, command); - - s.Reorder(command); - }); - } - - protected Task On(UpdateSchema command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, s => - { - GuardSchema.CanUpdate(s.Snapshot.SchemaDef, command); - - s.Update(command); - }); - } - - protected Task On(PublishSchema command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, s => - { - GuardSchema.CanPublish(s.Snapshot.SchemaDef, command); - - s.Publish(command); - }); - } - - protected Task On(UnpublishSchema command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, s => - { - GuardSchema.CanUnpublish(s.Snapshot.SchemaDef, command); - - s.Unpublish(command); - }); - } - - protected Task On(ConfigureScripts command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, s => - { - GuardSchema.CanConfigureScripts(s.Snapshot.SchemaDef, command); - - s.ConfigureScripts(command); - }); - } - - protected Task On(DeleteSchema command, CommandContext context) - { - return handler.UpdateSyncedAsync(context, s => - { - GuardSchema.CanDelete(s.Snapshot.SchemaDef, command); - - s.Delete(command); - }); - } - - public async Task HandleAsync(CommandContext context, Func next) - { - await this.DispatchActionAsync(context.Command, context); - await next(); - } - } -} diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs deleted file mode 100644 index b57b31333..000000000 --- a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs +++ /dev/null @@ -1,229 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.Generic; -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Schemas.Commands; -using Squidex.Domain.Apps.Entities.Schemas.State; -using Squidex.Domain.Apps.Events; -using Squidex.Domain.Apps.Events.Schemas; -using Squidex.Infrastructure; -using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.Reflection; - -namespace Squidex.Domain.Apps.Entities.Schemas -{ - public sealed class SchemaDomainObject : SquidexDomainObjectBase - { - private readonly FieldRegistry registry; - - public SchemaDomainObject(FieldRegistry registry) - { - Guard.NotNull(registry, nameof(registry)); - - this.registry = registry; - } - - public SchemaDomainObject Create(CreateSchema command) - { - VerifyNotCreated(); - - var @event = SimpleMapper.Map(command, new SchemaCreated { SchemaId = new NamedId(command.SchemaId, command.Name) }); - - if (command.Fields != null) - { - @event.Fields = new List(); - - foreach (var commandField in command.Fields) - { - var eventField = SimpleMapper.Map(commandField, new SchemaCreatedField()); - - @event.Fields.Add(eventField); - } - } - - RaiseEvent(@event); - - return this; - } - - public SchemaDomainObject Add(AddField command) - { - VerifyCreatedAndNotDeleted(); - - RaiseEvent(SimpleMapper.Map(command, new FieldAdded { FieldId = new NamedId(Snapshot.TotalFields + 1, command.Name) })); - - return this; - } - - public SchemaDomainObject UpdateField(UpdateField command) - { - VerifyCreatedAndNotDeleted(); - - RaiseEvent(command, SimpleMapper.Map(command, new FieldUpdated())); - - return this; - } - - public SchemaDomainObject LockField(LockField command) - { - VerifyCreatedAndNotDeleted(); - - RaiseEvent(command, new FieldLocked()); - - return this; - } - - public SchemaDomainObject HideField(HideField command) - { - VerifyCreatedAndNotDeleted(); - - RaiseEvent(command, new FieldHidden()); - - return this; - } - - public SchemaDomainObject ShowField(ShowField command) - { - VerifyCreatedAndNotDeleted(); - - RaiseEvent(command, new FieldShown()); - - return this; - } - - public SchemaDomainObject DisableField(DisableField command) - { - VerifyCreatedAndNotDeleted(); - - RaiseEvent(command, new FieldDisabled()); - - return this; - } - - public SchemaDomainObject EnableField(EnableField command) - { - VerifyCreatedAndNotDeleted(); - - RaiseEvent(command, new FieldEnabled()); - - return this; - } - - public SchemaDomainObject DeleteField(DeleteField command) - { - VerifyCreatedAndNotDeleted(); - - RaiseEvent(command, new FieldDeleted()); - - return this; - } - - public SchemaDomainObject Reorder(ReorderFields command) - { - VerifyCreatedAndNotDeleted(); - - RaiseEvent(SimpleMapper.Map(command, new SchemaFieldsReordered())); - - return this; - } - - public SchemaDomainObject Publish(PublishSchema command) - { - VerifyCreatedAndNotDeleted(); - - RaiseEvent(SimpleMapper.Map(command, new SchemaPublished())); - - return this; - } - - public SchemaDomainObject Unpublish(UnpublishSchema command) - { - VerifyCreatedAndNotDeleted(); - - RaiseEvent(SimpleMapper.Map(command, new SchemaUnpublished())); - - return this; - } - - public SchemaDomainObject ConfigureScripts(ConfigureScripts command) - { - VerifyCreatedAndNotDeleted(); - - RaiseEvent(SimpleMapper.Map(command, new ScriptsConfigured())); - - return this; - } - - public SchemaDomainObject Delete(DeleteSchema command) - { - VerifyCreatedAndNotDeleted(); - - RaiseEvent(SimpleMapper.Map(command, new SchemaDeleted())); - - return this; - } - - public SchemaDomainObject Update(UpdateSchema command) - { - VerifyCreatedAndNotDeleted(); - - RaiseEvent(SimpleMapper.Map(command, new SchemaUpdated())); - - return this; - } - - private void RaiseEvent(FieldCommand fieldCommand, FieldEvent @event) - { - SimpleMapper.Map(fieldCommand, @event); - - if (Snapshot.SchemaDef.FieldsById.TryGetValue(fieldCommand.FieldId, out var field)) - { - @event.FieldId = new NamedId(field.Id, field.Name); - } - - RaiseEvent(@event); - } - - private void RaiseEvent(SchemaEvent @event) - { - if (@event.SchemaId == null) - { - @event.SchemaId = new NamedId(Snapshot.Id, Snapshot.Name); - } - - if (@event.AppId == null) - { - @event.AppId = Snapshot.AppId; - } - - RaiseEvent(Envelope.Create(@event)); - } - - private void VerifyNotCreated() - { - if (Snapshot.SchemaDef != null) - { - throw new DomainException("Schema has already been created."); - } - } - - private void VerifyCreatedAndNotDeleted() - { - if (Snapshot.IsDeleted || Snapshot.SchemaDef == null) - { - throw new DomainException("Schema has already been deleted or not created yet."); - } - } - - public override void ApplyEvent(Envelope @event) - { - ApplySnapshot(Snapshot.Apply(@event, registry)); - } - } -} \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs new file mode 100644 index 000000000..332129c30 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs @@ -0,0 +1,330 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Entities.Schemas.Commands; +using Squidex.Domain.Apps.Entities.Schemas.Guards; +using Squidex.Domain.Apps.Entities.Schemas.State; +using Squidex.Domain.Apps.Events; +using Squidex.Domain.Apps.Events.Schemas; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Reflection; +using Squidex.Infrastructure.States; + +namespace Squidex.Domain.Apps.Entities.Schemas +{ + public sealed class SchemaGrain : DomainObjectGrain, ISchemaGrain + { + private readonly IAppProvider appProvider; + private readonly FieldRegistry registry; + + public SchemaGrain(IStore store, IAppProvider appProvider, FieldRegistry registry) + : base(store) + { + Guard.NotNull(appProvider, nameof(appProvider)); + Guard.NotNull(registry, nameof(registry)); + + this.appProvider = appProvider; + + this.registry = registry; + } + + public override Task ExecuteAsync(IAggregateCommand command) + { + switch (command) + { + case CreateSchema createSchema: + return CreateAsync(createSchema, async c => + { + await GuardSchema.CanCreate(c, appProvider); + + Create(c); + }); + + case AddField addField: + return UpdateReturnAsync(addField, c => + { + GuardSchemaField.CanAdd(Snapshot.SchemaDef, c); + + Add(c); + + return EntityCreatedResult.Create(Snapshot.SchemaDef.FieldsById.Values.First(x => x.Name == addField.Name).Id, Version); + }); + + case DeleteField deleteField: + return UpdateAsync(deleteField, c => + { + GuardSchemaField.CanDelete(Snapshot.SchemaDef, deleteField); + + DeleteField(c); + }); + + case LockField lockField: + return UpdateAsync(lockField, c => + { + GuardSchemaField.CanLock(Snapshot.SchemaDef, lockField); + + LockField(c); + }); + + case HideField hideField: + return UpdateAsync(hideField, c => + { + GuardSchemaField.CanHide(Snapshot.SchemaDef, c); + + HideField(c); + }); + + case ShowField showField: + return UpdateAsync(showField, c => + { + GuardSchemaField.CanShow(Snapshot.SchemaDef, c); + + ShowField(c); + }); + + case DisableField disableField: + return UpdateAsync(disableField, c => + { + GuardSchemaField.CanDisable(Snapshot.SchemaDef, c); + + DisableField(c); + }); + + case EnableField enableField: + return UpdateAsync(enableField, c => + { + GuardSchemaField.CanEnable(Snapshot.SchemaDef, c); + + EnableField(c); + }); + + case UpdateField updateField: + return UpdateAsync(updateField, c => + { + GuardSchemaField.CanUpdate(Snapshot.SchemaDef, c); + + UpdateField(c); + }); + + case ReorderFields reorderFields: + return UpdateAsync(reorderFields, c => + { + GuardSchema.CanReorder(Snapshot.SchemaDef, c); + + Reorder(c); + }); + + case UpdateSchema updateSchema: + return UpdateAsync(updateSchema, c => + { + GuardSchema.CanUpdate(Snapshot.SchemaDef, c); + + Update(c); + }); + + case PublishSchema publishSchema: + return UpdateAsync(publishSchema, c => + { + GuardSchema.CanPublish(Snapshot.SchemaDef, c); + + Publish(c); + }); + + case UnpublishSchema unpublishSchema: + return UpdateAsync(unpublishSchema, c => + { + GuardSchema.CanUnpublish(Snapshot.SchemaDef, c); + + Unpublish(c); + }); + + case ConfigureScripts configureScripts: + return UpdateAsync(configureScripts, c => + { + GuardSchema.CanConfigureScripts(Snapshot.SchemaDef, c); + + ConfigureScripts(c); + }); + + case DeleteSchema deleteSchema: + return UpdateAsync(deleteSchema, c => + { + GuardSchema.CanDelete(Snapshot.SchemaDef, c); + + Delete(c); + }); + + default: + throw new NotSupportedException(); + } + } + + public void Create(CreateSchema command) + { + var @event = SimpleMapper.Map(command, new SchemaCreated { SchemaId = new NamedId(command.SchemaId, command.Name) }); + + if (command.Fields != null) + { + @event.Fields = new List(); + + foreach (var commandField in command.Fields) + { + var eventField = SimpleMapper.Map(commandField, new SchemaCreatedField()); + + @event.Fields.Add(eventField); + } + } + + RaiseEvent(@event); + } + + public void Add(AddField command) + { + VerifyNotDeleted(); + + RaiseEvent(SimpleMapper.Map(command, new FieldAdded { FieldId = new NamedId(Snapshot.TotalFields + 1, command.Name) })); + } + + public void UpdateField(UpdateField command) + { + VerifyNotDeleted(); + + RaiseEvent(command, SimpleMapper.Map(command, new FieldUpdated())); + } + + public void LockField(LockField command) + { + VerifyNotDeleted(); + + RaiseEvent(command, new FieldLocked()); + } + + public void HideField(HideField command) + { + VerifyNotDeleted(); + + RaiseEvent(command, new FieldHidden()); + } + + public void ShowField(ShowField command) + { + VerifyNotDeleted(); + + RaiseEvent(command, new FieldShown()); + } + + public void DisableField(DisableField command) + { + VerifyNotDeleted(); + + RaiseEvent(command, new FieldDisabled()); + } + + public void EnableField(EnableField command) + { + VerifyNotDeleted(); + + RaiseEvent(command, new FieldEnabled()); + } + + public void DeleteField(DeleteField command) + { + VerifyNotDeleted(); + + RaiseEvent(command, new FieldDeleted()); + } + + public void Reorder(ReorderFields command) + { + VerifyNotDeleted(); + + RaiseEvent(SimpleMapper.Map(command, new SchemaFieldsReordered())); + } + + public void Publish(PublishSchema command) + { + VerifyNotDeleted(); + + RaiseEvent(SimpleMapper.Map(command, new SchemaPublished())); + } + + public void Unpublish(UnpublishSchema command) + { + VerifyNotDeleted(); + + RaiseEvent(SimpleMapper.Map(command, new SchemaUnpublished())); + } + + public void ConfigureScripts(ConfigureScripts command) + { + VerifyNotDeleted(); + + RaiseEvent(SimpleMapper.Map(command, new ScriptsConfigured())); + } + + public void Delete(DeleteSchema command) + { + VerifyNotDeleted(); + + RaiseEvent(SimpleMapper.Map(command, new SchemaDeleted())); + } + + public void Update(UpdateSchema command) + { + VerifyNotDeleted(); + + RaiseEvent(SimpleMapper.Map(command, new SchemaUpdated())); + } + + private void RaiseEvent(FieldCommand fieldCommand, FieldEvent @event) + { + SimpleMapper.Map(fieldCommand, @event); + + if (Snapshot.SchemaDef.FieldsById.TryGetValue(fieldCommand.FieldId, out var field)) + { + @event.FieldId = new NamedId(field.Id, field.Name); + } + + RaiseEvent(@event); + } + + private void RaiseEvent(SchemaEvent @event) + { + if (@event.SchemaId == null) + { + @event.SchemaId = new NamedId(Snapshot.Id, Snapshot.Name); + } + + if (@event.AppId == null) + { + @event.AppId = Snapshot.AppId; + } + + RaiseEvent(Envelope.Create(@event)); + } + + private void VerifyNotDeleted() + { + if (Snapshot.IsDeleted) + { + throw new DomainException("Schema has already been deleted."); + } + } + + public override void ApplyEvent(Envelope @event) + { + ApplySnapshot(Snapshot.Apply(@event, registry)); + } + } +} \ No newline at end of file diff --git a/src/Squidex.Infrastructure/Assets/AssetFile.cs b/src/Squidex.Infrastructure/Assets/AssetFile.cs index 3b59ba505..bbaa0917d 100644 --- a/src/Squidex.Infrastructure/Assets/AssetFile.cs +++ b/src/Squidex.Infrastructure/Assets/AssetFile.cs @@ -7,6 +7,7 @@ using System; using System.IO; +using Newtonsoft.Json; namespace Squidex.Infrastructure.Assets { @@ -20,11 +21,11 @@ namespace Squidex.Infrastructure.Assets public long FileSize { get; } + [JsonConstructor] public AssetFile(string fileName, string mimeType, long fileSize, Func openAction) { Guard.NotNullOrEmpty(fileName, nameof(fileName)); Guard.NotNullOrEmpty(mimeType, nameof(mimeType)); - Guard.NotNull(openAction, nameof(openAction)); Guard.GreaterEquals(fileSize, 0, nameof(fileSize)); FileName = fileName; diff --git a/src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs b/src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs new file mode 100644 index 000000000..16636ef8b --- /dev/null +++ b/src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs @@ -0,0 +1,204 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Orleans; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Orleans; +using Squidex.Infrastructure.States; + +namespace Squidex.Infrastructure.Commands +{ + public abstract class DomainObjectGrain : Grain, IDomainObjectGrain where T : IDomainState, new() + { + private readonly List> uncomittedEvents = new List>(); + private readonly IStore store; + private T snapshot = new T { Version = EtagVersion.Empty }; + private IPersistence persistence; + + public long Version + { + get { return snapshot.Version; } + } + + public long NewVersion + { + get { return snapshot.Version + uncomittedEvents.Count; } + } + + protected T Snapshot + { + get { return snapshot; } + } + + protected DomainObjectGrain(IStore store) + { + Guard.NotNull(store, nameof(store)); + + this.store = store; + } + + public override Task OnActivateAsync() + { + persistence = store.WithSnapshotsAndEventSourcing(GetType(), this.GetPrimaryKey(), ApplySnapshot, ApplyEvent); + + return persistence.ReadAsync(); + } + + public void RaiseEvent(IEvent @event) + { + RaiseEvent(Envelope.Create(@event)); + } + + public virtual void RaiseEvent(Envelope @event) + { + Guard.NotNull(@event, nameof(@event)); + + @event.SetAggregateId(this.GetPrimaryKey()); + + ApplyEvent(@event); + + uncomittedEvents.Add(@event); + } + + public IReadOnlyList> GetUncomittedEvents() + { + return uncomittedEvents; + } + + public void ClearUncommittedEvents() + { + uncomittedEvents.Clear(); + } + + public virtual void ApplySnapshot(T newSnapshot) + { + snapshot = newSnapshot; + } + + public virtual void ApplyEvent(Envelope @event) + { + } + + public Task WriteSnapshotAsync() + { + snapshot.Version = persistence.Version; + + return persistence.WriteSnapshotAsync(snapshot); + } + + protected Task CreateReturnAsync(TCommand command, Func> handler) where TCommand : class, IAggregateCommand + { + return InvokeAsync(command, handler, false); + } + + protected Task CreateReturnAsync(TCommand command, Func handler) where TCommand : class, IAggregateCommand + { + return InvokeAsync(command, x => Task.FromResult(handler(x)), false); + } + + protected Task CreateAsync(TCommand command, Func handler) where TCommand : class, IAggregateCommand + { + return InvokeAsync(command, x => handler(x).ContinueWith(t => null), false); + } + + protected Task CreateAsync(TCommand command, Action handler) where TCommand : class, IAggregateCommand + { + return InvokeAsync(command, x => { handler(x); return Task.FromResult(null); }, false); + } + + protected Task UpdateReturnAsync(TCommand command, Func> handler) where TCommand : class, IAggregateCommand + { + return InvokeAsync(command, handler, true); + } + + protected Task UpdateReturnAsync(TCommand command, Func handler) where TCommand : class, IAggregateCommand + { + return InvokeAsync(command, x => Task.FromResult(handler(x)), true); + } + + protected Task UpdateAsync(TCommand command, Func handler) where TCommand : class, IAggregateCommand + { + return InvokeAsync(command, x => handler(x).ContinueWith(t => null), true); + } + + protected Task UpdateAsync(TCommand command, Action handler) where TCommand : class, IAggregateCommand + { + return InvokeAsync(command, x => { handler(x); return Task.FromResult(null); }, true); + } + + private async Task InvokeAsync(TCommand command, Func> handler, bool isUpdate) where TCommand : class, IAggregateCommand + { + Guard.NotNull(command, nameof(command)); + + if (command.ExpectedVersion != EtagVersion.Any && command.ExpectedVersion != Version) + { + throw new DomainObjectVersionException(this.GetPrimaryKey().ToString(), GetType(), Version, command.ExpectedVersion); + } + + if (isUpdate && Version < 0) + { + DeactivateOnIdle(); + + throw new DomainObjectNotFoundException(this.GetPrimaryKey().ToString(), GetType()); + } + else if (!isUpdate && Version >= 0) + { + throw new DomainException("Object has already been created."); + } + + var previousSnapshot = snapshot; + try + { + var result = await handler(command); + + var events = uncomittedEvents.ToArray(); + + if (events.Length > 0) + { + snapshot.Version = NewVersion; + + await persistence.WriteEventsAsync(events); + await persistence.WriteSnapshotAsync(snapshot); + } + + if (result == null) + { + if (isUpdate) + { + result = new EntitySavedResult(Version); + } + else + { + result = EntityCreatedResult.Create(this.GetPrimaryKey(), Version); + } + } + + return result; + } + catch + { + snapshot = previousSnapshot; + + throw; + } + finally + { + uncomittedEvents.Clear(); + } + } + + public Task> ExecuteAsync(J command) + { + return ExecuteAsync(command.Value).ContinueWith(x => x.Result.AsJ()); + } + + public abstract Task ExecuteAsync(IAggregateCommand command); + } +} diff --git a/src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs b/src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs new file mode 100644 index 000000000..97ebe0b0d --- /dev/null +++ b/src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs @@ -0,0 +1,46 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using Orleans; + +namespace Squidex.Infrastructure.Commands +{ + public class GrainCommandMiddleware : ICommandMiddleware where TCommand : IAggregateCommand where TGrain : IDomainObjectGrain + { + private readonly IGrainFactory grainFactory; + + public GrainCommandMiddleware(IGrainFactory grainFactory) + { + Guard.NotNull(grainFactory, nameof(grainFactory)); + + this.grainFactory = grainFactory; + } + + public async virtual Task HandleAsync(CommandContext context, Func next) + { + if (context.Command is TCommand typedCommand) + { + var result = await ExecuteCommandAsync(typedCommand); + + context.Complete(result); + } + + await next(); + } + + protected async Task ExecuteCommandAsync(TCommand typedCommand) + { + var grain = grainFactory.GetGrain(typedCommand.AggregateId); + + var result = await grain.ExecuteAsync(typedCommand); + + return result.Value; + } + } +} diff --git a/src/Squidex.Infrastructure/Commands/IDomainObjectGrain.cs b/src/Squidex.Infrastructure/Commands/IDomainObjectGrain.cs new file mode 100644 index 000000000..500db0245 --- /dev/null +++ b/src/Squidex.Infrastructure/Commands/IDomainObjectGrain.cs @@ -0,0 +1,20 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Threading.Tasks; +using Orleans; +using Squidex.Infrastructure.Orleans; + +namespace Squidex.Infrastructure.Commands +{ + public interface IDomainObjectGrain : IGrainWithGuidKey + { + Task WriteSnapshotAsync(); + + Task> ExecuteAsync(J command); + } +} diff --git a/src/Squidex.Infrastructure/Orleans/J.cs b/src/Squidex.Infrastructure/Orleans/J.cs new file mode 100644 index 000000000..cd1190790 --- /dev/null +++ b/src/Squidex.Infrastructure/Orleans/J.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Newtonsoft.Json; + +#pragma warning disable SA1401 // Fields must be private + +namespace Squidex.Infrastructure.Orleans +{ + public static class J + { + public static JsonSerializer Serializer = new JsonSerializer(); + } +} diff --git a/src/Squidex.Infrastructure/Orleans/JExtensions.cs b/src/Squidex.Infrastructure/Orleans/JExtensions.cs new file mode 100644 index 000000000..49125b346 --- /dev/null +++ b/src/Squidex.Infrastructure/Orleans/JExtensions.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Infrastructure.Orleans +{ + public static class JExtensions + { + public static J AsJ(this T value) + { + return new J(value); + } + } +} diff --git a/src/Squidex.Infrastructure/Orleans/J{T}.cs b/src/Squidex.Infrastructure/Orleans/J{T}.cs new file mode 100644 index 000000000..7d7329998 --- /dev/null +++ b/src/Squidex.Infrastructure/Orleans/J{T}.cs @@ -0,0 +1,85 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.IO; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Orleans.CodeGeneration; +using Orleans.Serialization; + +namespace Squidex.Infrastructure.Orleans +{ + public struct J + { + private readonly T value; + + public T Value + { + get { return value; } + } + + [JsonConstructor] + public J(T value) + { + this.value = value; + } + + 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); + } + + [CopierMethod] + private static object Copy(object input, ICopyContext context) + { + return input; + } + + [SerializerMethod] + private static void Serialize(object input, ISerializationContext context, Type expected) + { + var stream = new MemoryStream(); + + using (var writer = new JsonTextWriter(new StreamWriter(stream))) + { + J.Serializer.Serialize(writer, input); + + writer.Flush(); + } + + var outBytes = stream.ToArray(); + + context.StreamWriter.Write(outBytes.Length); + context.StreamWriter.Write(outBytes); + } + + [DeserializerMethod] + private static object Deserialize(Type expected, IDeserializationContext context) + { + var outLength = context.StreamReader.ReadInt(); + var outBytes = context.StreamReader.ReadBytes(outLength); + + var stream = new MemoryStream(outBytes); + + using (var reader = new JsonTextReader(new StreamReader(stream))) + { + return J.Serializer.Deserialize(reader, expected); + } + } + } +} diff --git a/src/Squidex/Config/Domain/SerializationServices.cs b/src/Squidex/Config/Domain/SerializationServices.cs index f783f2601..45e7ad011 100644 --- a/src/Squidex/Config/Domain/SerializationServices.cs +++ b/src/Squidex/Config/Domain/SerializationServices.cs @@ -27,15 +27,17 @@ namespace Squidex.Config.Domain public static class SerializationServices { private static readonly TypeNameRegistry TypeNameRegistry = - new TypeNameRegistry() - .MapUnmapped(SquidexCoreModel.Assembly) - .MapUnmapped(SquidexEvents.Assembly) - .MapUnmapped(SquidexInfrastructure.Assembly) - .MapUnmapped(SquidexMigrations.Assembly); - + new TypeNameRegistry() + .MapUnmapped(SquidexCoreModel.Assembly) + .MapUnmapped(SquidexEvents.Assembly) + .MapUnmapped(SquidexInfrastructure.Assembly) + .MapUnmapped(SquidexMigrations.Assembly); private static readonly FieldRegistry FieldRegistry = new FieldRegistry(TypeNameRegistry); - private static JsonSerializerSettings ConfigureJson(JsonSerializerSettings settings, TypeNameHandling typeNameHandling) + public static readonly JsonSerializerSettings DefaultJsonSettings = new JsonSerializerSettings(); + public static readonly JsonSerializer DefaultJsonSerializer; + + private static void ConfigureJson(JsonSerializerSettings settings, TypeNameHandling typeNameHandling) { settings.SerializationBinder = new TypeNameSerializationBinder(TypeNameRegistry); @@ -65,22 +67,24 @@ namespace Squidex.Config.Domain settings.TypeNameHandling = typeNameHandling; settings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); + } + + static SerializationServices() + { + ConfigureJson(DefaultJsonSettings, TypeNameHandling.Auto); + + DefaultJsonSerializer = JsonSerializer.Create(DefaultJsonSettings); - return settings; + BsonJsonConvention.Register(DefaultJsonSerializer); } public static IServiceCollection AddMySerializers(this IServiceCollection services) { - var serializerSettings = ConfigureJson(new JsonSerializerSettings(), TypeNameHandling.Auto); - var serializerInstance = JsonSerializer.Create(serializerSettings); - services.AddSingletonAs(t => FieldRegistry); - services.AddSingletonAs(t => serializerSettings); - services.AddSingletonAs(t => serializerInstance); + services.AddSingletonAs(t => DefaultJsonSettings); + services.AddSingletonAs(t => DefaultJsonSerializer); services.AddSingletonAs(t => TypeNameRegistry); - BsonJsonConvention.Register(serializerInstance); - return services; } diff --git a/src/Squidex/Config/Domain/WriteServices.cs b/src/Squidex/Config/Domain/WriteServices.cs index a40bd1c87..b1d1f0423 100644 --- a/src/Squidex/Config/Domain/WriteServices.cs +++ b/src/Squidex/Config/Domain/WriteServices.cs @@ -12,11 +12,13 @@ using Migrate_01; using Migrate_01.Migrations; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Scripting; +using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Templates; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Rules; +using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Users; using Squidex.Infrastructure.Commands; @@ -59,10 +61,10 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); - services.AddSingletonAs() + services.AddSingletonAs>() .As(); - services.AddSingletonAs() + services.AddSingletonAs>() .As(); services.AddSingletonAs() @@ -92,19 +94,10 @@ namespace Squidex.Config.Domain services.AddTransientAs() .AsSelf(); - services.AddTransientAs() - .AsSelf(); - services.AddTransientAs() .AsSelf(); - services.AddTransientAs() - .AsSelf(); - - services.AddTransientAs() - .AsSelf(); - - services.AddSingleton(c => + services.AddSingleton(c => { var config = c.GetRequiredService>(); diff --git a/src/Squidex/Config/Orleans/SiloWrapper.cs b/src/Squidex/Config/Orleans/SiloWrapper.cs index 75b2dfd67..7e700fdf4 100644 --- a/src/Squidex/Config/Orleans/SiloWrapper.cs +++ b/src/Squidex/Config/Orleans/SiloWrapper.cs @@ -11,9 +11,11 @@ using Microsoft.Extensions.Configuration; using Orleans; using Orleans.Hosting; using Orleans.Runtime.Configuration; +using Squidex.Config.Domain; using Squidex.Domain.Apps.Entities; using Squidex.Infrastructure; using Squidex.Infrastructure.Log.Adapter; +using Squidex.Infrastructure.Orleans; namespace Squidex.Config.Orleans { @@ -38,6 +40,8 @@ namespace Squidex.Config.Orleans public SiloWrapper(IConfiguration configuration) { + J.Serializer = SerializationServices.DefaultJsonSerializer; + silo = SiloHostBuilder.CreateDefault() .UseConfiguration(ClusterConfiguration.LocalhostPrimarySilo(33333).WithDashboard()) .UseContentRoot(Directory.GetCurrentDirectory())