diff --git a/libs/orleansdashboard/2.0.0-rc1/orleansdashboard.2.0.0-rc1.nupkg b/libs/orleansdashboard/2.0.0-rc1/orleansdashboard.2.0.0-rc1.nupkg
new file mode 100644
index 000000000..b2edaddf6
Binary files /dev/null and b/libs/orleansdashboard/2.0.0-rc1/orleansdashboard.2.0.0-rc1.nupkg differ
diff --git a/libs/orleansdashboard/2.0.0-rc1/orleansdashboard.2.0.0-rc1.nupkg.sha512 b/libs/orleansdashboard/2.0.0-rc1/orleansdashboard.2.0.0-rc1.nupkg.sha512
new file mode 100644
index 000000000..540b97178
--- /dev/null
+++ b/libs/orleansdashboard/2.0.0-rc1/orleansdashboard.2.0.0-rc1.nupkg.sha512
@@ -0,0 +1 @@
+oeHEL1XH6DwEv4Rk6JjAABzcpTdBI3Zmoz3tyn+20vBUcvsdmKQMFp8I1rBZmAeOJ9NSvvRYf8LHDM2UtRTbvw==
\ No newline at end of file
diff --git a/libs/orleansdashboard/2.0.0-rc1/orleansdashboard.nuspec b/libs/orleansdashboard/2.0.0-rc1/orleansdashboard.nuspec
new file mode 100644
index 000000000..e31eec4a6
--- /dev/null
+++ b/libs/orleansdashboard/2.0.0-rc1/orleansdashboard.nuspec
@@ -0,0 +1,25 @@
+
+
+
+ OrleansDashboard
+ 2.0.0-rc1
+ OrleansContrib
+ OrleansContrib
+ false
+ https://opensource.org/licenses/MIT
+ https://github.com/OrleansContrib/OrleansDashboard
+ http://dotnet.github.io/orleans/assets/logo.png
+ An admin dashboard for Microsoft Orleans
+ Copyright © 2017
+ orleans dashboard metrics monitor
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj b/src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj
index 91ab5c926..56ec1fe60 100644
--- a/src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj
+++ b/src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj
@@ -8,7 +8,7 @@
True
-
+
diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs
index 5fe661d4f..bac9ef7fd 100644
--- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs
+++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs
@@ -63,7 +63,14 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
var responseString = await response.Content.ReadAsStringAsync();
var requestDump = DumpFormatter.BuildDump(requestMsg, response, requestBody, responseString, TimeSpan.Zero, false);
- return (requestDump, null);
+ Exception ex = null;
+
+ if (!response.IsSuccessStatusCode)
+ {
+ ex = new HttpRequestException($"Response code does not indicate success: {(int)response.StatusCode} ({response.StatusCode}).");
+ }
+
+ return (requestDump, ex);
}
catch (Exception ex)
{
diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs
index 13ca5f459..6914f3d44 100644
--- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs
+++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs
@@ -17,7 +17,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Triggers
{
protected override bool Triggers(Envelope @event, ContentChangedTrigger trigger)
{
- if (trigger.HandleAll)
+ if (trigger.HandleAll && @event.Payload is ContentEvent)
{
return true;
}
diff --git a/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj b/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
index 4c836c4ee..413191771 100644
--- a/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
+++ b/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
@@ -13,18 +13,18 @@
-
-
+
+
-
-
-
-
+
+
+
+
-
+
..\..\Squidex.ruleset
diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj b/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj
index 1095818b8..c63141855 100644
--- a/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj
+++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj
@@ -15,7 +15,7 @@
-
+
diff --git a/src/Squidex.Domain.Apps.Entities/AppProvider.cs b/src/Squidex.Domain.Apps.Entities/AppProvider.cs
index 7439e4a1e..29a4e9283 100644
--- a/src/Squidex.Domain.Apps.Entities/AppProvider.cs
+++ b/src/Squidex.Domain.Apps.Entities/AppProvider.cs
@@ -9,6 +9,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using Orleans;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Repositories;
using Squidex.Domain.Apps.Entities.Rules;
@@ -16,52 +17,50 @@ using Squidex.Domain.Apps.Entities.Rules.Repositories;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Repositories;
using Squidex.Infrastructure;
-using Squidex.Infrastructure.Commands;
-using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities
{
public sealed class AppProvider : IAppProvider
{
+ private readonly IGrainFactory grainFactory;
private readonly IAppRepository appRepository;
private readonly IRuleRepository ruleRepository;
private readonly ISchemaRepository schemaRepository;
- private readonly IStateFactory stateFactory;
public AppProvider(
+ IGrainFactory grainFactory,
IAppRepository appRepository,
ISchemaRepository schemaRepository,
- IStateFactory stateFactory,
IRuleRepository ruleRepository)
{
+ Guard.NotNull(grainFactory, nameof(grainFactory));
Guard.NotNull(appRepository, nameof(appRepository));
Guard.NotNull(schemaRepository, nameof(schemaRepository));
- Guard.NotNull(stateFactory, nameof(stateFactory));
Guard.NotNull(ruleRepository, nameof(ruleRepository));
+ this.grainFactory = grainFactory;
this.appRepository = appRepository;
this.schemaRepository = schemaRepository;
- this.stateFactory = stateFactory;
this.ruleRepository = ruleRepository;
}
public async Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(Guid appId, Guid id)
{
- var app = await stateFactory.GetSingleAsync(appId);
+ var app = await grainFactory.GetGrain(appId).GetStateAsync();
- if (!IsFound(app))
+ if (!IsFound(app.Value))
{
return (null, null);
}
- var schema = await stateFactory.GetSingleAsync(id);
+ var schema = await grainFactory.GetGrain(id).GetStateAsync();
- if (!IsFound(schema) || schema.Snapshot.IsDeleted)
+ if (!IsFound(schema.Value) || schema.Value.IsDeleted)
{
return (null, null);
}
- return (app.Snapshot, schema.Snapshot);
+ return (app.Value, schema.Value);
}
public async Task GetAppAsync(string appName)
@@ -73,7 +72,7 @@ namespace Squidex.Domain.Apps.Entities
return null;
}
- return (await stateFactory.GetSingleAsync(appId)).Snapshot;
+ return (await grainFactory.GetGrain(appId).GetStateAsync()).Value;
}
public async Task GetSchemaAsync(Guid appId, string name)
@@ -85,19 +84,19 @@ namespace Squidex.Domain.Apps.Entities
return null;
}
- return (await stateFactory.GetSingleAsync(schemaId)).Snapshot;
+ return (await grainFactory.GetGrain(schemaId).GetStateAsync()).Value;
}
public async Task GetSchemaAsync(Guid appId, Guid id, bool allowDeleted = false)
{
- var schema = await stateFactory.GetSingleAsync(id);
+ var schema = await grainFactory.GetGrain(id).GetStateAsync();
- if (!IsFound(schema) || (schema.Snapshot.IsDeleted && !allowDeleted) || schema.Snapshot.AppId.Id != appId)
+ if (!IsFound(schema.Value) || (schema.Value.IsDeleted && !allowDeleted) || schema.Value.AppId.Id != appId)
{
return null;
}
- return schema.Snapshot;
+ return schema.Value;
}
public async Task> GetSchemasAsync(Guid appId)
@@ -106,9 +105,9 @@ namespace Squidex.Domain.Apps.Entities
var schemas =
await Task.WhenAll(
- ids.Select(id => stateFactory.GetSingleAsync(id)));
+ ids.Select(id => grainFactory.GetGrain(id).GetStateAsync()));
- return schemas.Where(IsFound).Select(s => (ISchemaEntity)s.Snapshot).ToList();
+ return schemas.Where(s => IsFound(s.Value)).Select(s => (ISchemaEntity)s.Value).ToList();
}
public async Task> GetRulesAsync(Guid appId)
@@ -117,9 +116,9 @@ namespace Squidex.Domain.Apps.Entities
var rules =
await Task.WhenAll(
- ids.Select(id => stateFactory.GetSingleAsync(id)));
+ ids.Select(id => grainFactory.GetGrain(id).GetStateAsync()));
- return rules.Where(IsFound).Select(r => (IRuleEntity)r.Snapshot).ToList();
+ return rules.Where(r => IsFound(r.Value)).Select(r => (IRuleEntity)r.Value).ToList();
}
public async Task> GetUserApps(string userId)
@@ -128,9 +127,9 @@ namespace Squidex.Domain.Apps.Entities
var apps =
await Task.WhenAll(
- ids.Select(id => stateFactory.GetSingleAsync(id)));
+ ids.Select(id => grainFactory.GetGrain(id).GetStateAsync()));
- return apps.Where(IsFound).Select(a => (IAppEntity)a.Snapshot).ToList();
+ return apps.Where(a => IsFound(a.Value)).Select(a => (IAppEntity)a.Value).ToList();
}
private Task GetAppIdAsync(string name)
@@ -143,9 +142,9 @@ namespace Squidex.Domain.Apps.Entities
return await schemaRepository.FindSchemaIdAsync(appId, name);
}
- private static bool IsFound(IDomainObjectGrain app)
+ private static bool IsFound(IEntityWithVersion entity)
{
- return app.Version > EtagVersion.Empty;
+ return entity.Version > EtagVersion.Empty;
}
}
}
diff --git a/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs
index 50a342d7e..eb9a3b886 100644
--- a/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs
+++ b/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs
@@ -18,13 +18,14 @@ using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing;
+using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Apps
{
- public class AppGrain : DomainObjectGrain
+ public class AppGrain : DomainObjectGrain, IAppGrain
{
private readonly InitialPatterns initialPatterns;
private readonly IAppProvider appProvider;
@@ -54,7 +55,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
this.initialPatterns = initialPatterns;
}
- public override Task
-
+
+
diff --git a/src/Squidex.Infrastructure/EventSourcing/Grains/Messages/ResetConsumerMessage.cs b/src/Squidex.Domain.Apps.Entities/SquidexEntities.cs
similarity index 58%
rename from src/Squidex.Infrastructure/EventSourcing/Grains/Messages/ResetConsumerMessage.cs
rename to src/Squidex.Domain.Apps.Entities/SquidexEntities.cs
index 012cfd2e1..c65786a8d 100644
--- a/src/Squidex.Infrastructure/EventSourcing/Grains/Messages/ResetConsumerMessage.cs
+++ b/src/Squidex.Domain.Apps.Entities/SquidexEntities.cs
@@ -1,14 +1,16 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
-// Copyright (c) Squidex UG (haftungsbeschränkt)
+// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
-namespace Squidex.Infrastructure.EventSourcing.Grains.Messages
+using System.Reflection;
+
+namespace Squidex.Domain.Apps.Entities
{
- public sealed class ResetConsumerMessage
+ public sealed class SquidexEntities
{
- public string ConsumerName { get; set; }
+ public static readonly Assembly Assembly = typeof(SquidexEntities).Assembly;
}
}
diff --git a/src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj b/src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj
index 456d82e06..9c578c330 100644
--- a/src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj
+++ b/src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj
@@ -12,8 +12,8 @@
-
-
+
+
diff --git a/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj b/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj
index 50c66cb45..fce1bc788 100644
--- a/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj
+++ b/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj
@@ -13,8 +13,8 @@
-
-
+
+
diff --git a/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj b/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj
index 10b6e916a..0ca9385ae 100644
--- a/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj
+++ b/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj b/src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj
index 7f009bb3d..3e4bd2895 100644
--- a/src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj
+++ b/src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs b/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs
index cbf1559f5..b3cfed490 100644
--- a/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs
+++ b/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs
@@ -45,6 +45,10 @@ namespace Squidex.Infrastructure.EventSourcing
return TaskHelper.Done;
}
+ public void WakeUp()
+ {
+ }
+
private EventStoreCatchUpSubscription SubscribeToStream(string streamName)
{
var settings = CatchUpSubscriptionSettings.Default;
diff --git a/src/Squidex.Infrastructure.GetEventStore/Squidex.Infrastructure.GetEventStore.csproj b/src/Squidex.Infrastructure.GetEventStore/Squidex.Infrastructure.GetEventStore.csproj
index 4ebcb4f28..8a0943f3c 100644
--- a/src/Squidex.Infrastructure.GetEventStore/Squidex.Infrastructure.GetEventStore.csproj
+++ b/src/Squidex.Infrastructure.GetEventStore/Squidex.Infrastructure.GetEventStore.csproj
@@ -8,7 +8,7 @@
True
-
+
diff --git a/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEvent.cs b/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEvent.cs
index c33e7aee7..395fa594f 100644
--- a/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEvent.cs
+++ b/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEvent.cs
@@ -7,6 +7,7 @@
using MongoDB.Bson.Serialization.Attributes;
using Newtonsoft.Json.Linq;
+using Squidex.Infrastructure.MongoDb;
namespace Squidex.Infrastructure.EventSourcing
{
@@ -16,7 +17,7 @@ namespace Squidex.Infrastructure.EventSourcing
[BsonRequired]
public string Type { get; set; }
- [BsonElement]
+ [BsonJson]
[BsonRequired]
public string Payload { get; set; }
diff --git a/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Reader.cs b/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Reader.cs
index 175b74942..d81be0421 100644
--- a/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Reader.cs
+++ b/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Reader.cs
@@ -26,7 +26,7 @@ namespace Squidex.Infrastructure.EventSourcing
Guard.NotNull(subscriber, nameof(subscriber));
Guard.NotNullOrEmpty(streamFilter, nameof(streamFilter));
- return new PollingSubscription(this, notifier, subscriber, streamFilter, position);
+ return new PollingSubscription(this, subscriber, streamFilter, position);
}
public async Task> QueryAsync(string streamName, long streamPosition = 0)
diff --git a/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs b/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs
index 2a8d6e572..77019a716 100644
--- a/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs
+++ b/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs
@@ -49,4 +49,4 @@ namespace Squidex.Infrastructure.MongoDb
ConventionRegistry.Register("json", pack, t => true);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonSerializer.cs b/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonSerializer.cs
index d06df02c9..eefb3e3e5 100644
--- a/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonSerializer.cs
+++ b/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonSerializer.cs
@@ -12,7 +12,7 @@ using Newtonsoft.Json;
namespace Squidex.Infrastructure.MongoDb
{
- public class BsonJsonSerializer : ClassSerializerBase where T : class
+ public sealed class BsonJsonSerializer : ClassSerializerBase where T : class
{
private readonly JsonSerializer serializer;
diff --git a/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonWriter.cs b/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonWriter.cs
index dcf598616..52b3ac5d3 100644
--- a/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonWriter.cs
+++ b/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonWriter.cs
@@ -6,6 +6,7 @@
// ==========================================================================
using System;
+using System.Globalization;
using MongoDB.Bson.IO;
using NewtonsoftJSonWriter = Newtonsoft.Json.JsonWriter;
@@ -134,12 +135,12 @@ namespace Squidex.Infrastructure.MongoDb
public override void WriteValue(DateTime value)
{
- bsonWriter.WriteString(value.ToString());
+ bsonWriter.WriteString(value.ToString(CultureInfo.InvariantCulture));
}
public override void WriteValue(DateTimeOffset value)
{
- bsonWriter.WriteString(value.ToString());
+ bsonWriter.WriteString(value.ToString(CultureInfo.InvariantCulture));
}
public override void WriteValue(byte[] value)
diff --git a/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj b/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj
index e7f9a5feb..bef0bc3ae 100644
--- a/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj
+++ b/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj
@@ -11,7 +11,7 @@
-
+
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
index 6d6768581..339c2ebd6 100644
--- a/src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs
+++ b/src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs
@@ -9,12 +9,13 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Infrastructure.EventSourcing;
+using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.Commands
{
- public abstract class DomainObjectGrain : IDomainObjectGrain where T : IDomainState, new()
+ public abstract class DomainObjectGrain : GrainOfGuid, IDomainObjectGrain where T : IDomainState, new()
{
private readonly List> uncomittedEvents = new List>();
private readonly IStore store;
@@ -49,11 +50,11 @@ namespace Squidex.Infrastructure.Commands
this.store = store;
}
- public Task ActivateAsync(Guid key)
+ public override Task OnActivateAsync(Guid key)
{
id = key;
- persistence = store.WithSnapshotsAndEventSourcing(GetType(), key, ApplySnapshot, ApplyEvent);
+ persistence = store.WithSnapshotsAndEventSourcing(GetType(), id, ApplySnapshot, ApplyEvent);
return persistence.ReadAsync();
}
@@ -67,7 +68,7 @@ namespace Squidex.Infrastructure.Commands
{
Guard.NotNull(@event, nameof(@event));
- @event.SetAggregateId(Id);
+ @event.SetAggregateId(id);
ApplyEvent(@event);
@@ -146,12 +147,20 @@ namespace Squidex.Infrastructure.Commands
if (command.ExpectedVersion != EtagVersion.Any && command.ExpectedVersion != Version)
{
- throw new DomainObjectVersionException(Id.ToString(), GetType(), Version, command.ExpectedVersion);
+ throw new DomainObjectVersionException(id.ToString(), GetType(), Version, command.ExpectedVersion);
}
if (isUpdate && Version < 0)
{
- throw new DomainObjectNotFoundException(Id.ToString(), GetType());
+ try
+ {
+ DeactivateOnIdle();
+ }
+ catch (InvalidOperationException)
+ {
+ }
+
+ throw new DomainObjectNotFoundException(id.ToString(), GetType());
}
else if (!isUpdate && Version >= 0)
{
@@ -181,7 +190,7 @@ namespace Squidex.Infrastructure.Commands
}
else
{
- result = EntityCreatedResult.Create(Id, Version);
+ result = EntityCreatedResult.Create(id, Version);
}
}
@@ -195,10 +204,17 @@ namespace Squidex.Infrastructure.Commands
}
finally
{
- ClearUncommittedEvents();
+ uncomittedEvents.Clear();
}
}
- public abstract Task ExecuteAsync(IAggregateCommand command);
+ public async Task> ExecuteAsync(J command)
+ {
+ var result = await ExecuteAsync(command.Value);
+
+ return result.AsJ();
+ }
+
+ protected abstract Task ExecuteAsync(IAggregateCommand command);
}
}
diff --git a/src/Squidex.Infrastructure/Commands/DomainObjectGrainFormatter.cs b/src/Squidex.Infrastructure/Commands/DomainObjectGrainFormatter.cs
new file mode 100644
index 000000000..6d0da2184
--- /dev/null
+++ b/src/Squidex.Infrastructure/Commands/DomainObjectGrainFormatter.cs
@@ -0,0 +1,36 @@
+// ==========================================================================
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex UG (haftungsbeschraenkt)
+// All rights reserved. Licensed under the MIT license.
+// ==========================================================================
+
+using System;
+using Orleans;
+
+namespace Squidex.Infrastructure.Commands
+{
+ public static class DomainObjectGrainFormatter
+ {
+ public static string Format(IGrainCallContext context)
+ {
+ if (context.Method == null)
+ {
+ return "Unknown";
+ }
+
+ if (string.Equals(context.Method.Name, nameof(IDomainObjectGrain.ExecuteAsync), StringComparison.CurrentCultureIgnoreCase) &&
+ context.Arguments?.Length == 1 &&
+ context.Arguments[0] != null)
+ {
+ var argumentFullName = context.Arguments[0].ToString();
+ var argumentParts = argumentFullName.Split('.');
+ var argumentName = argumentParts[argumentParts.Length - 1];
+
+ return $"{nameof(IDomainObjectGrain.ExecuteAsync)}({argumentName})";
+ }
+
+ return context.Method.Name;
+ }
+ }
+}
diff --git a/src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs b/src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs
index a6ef41363..97ebe0b0d 100644
--- a/src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs
+++ b/src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs
@@ -7,19 +7,19 @@
using System;
using System.Threading.Tasks;
-using Squidex.Infrastructure.States;
+using Orleans;
namespace Squidex.Infrastructure.Commands
{
public class GrainCommandMiddleware : ICommandMiddleware where TCommand : IAggregateCommand where TGrain : IDomainObjectGrain
{
- private readonly IStateFactory stateFactory;
+ private readonly IGrainFactory grainFactory;
- public GrainCommandMiddleware(IStateFactory stateFactory)
+ public GrainCommandMiddleware(IGrainFactory grainFactory)
{
- Guard.NotNull(stateFactory, nameof(stateFactory));
+ Guard.NotNull(grainFactory, nameof(grainFactory));
- this.stateFactory = stateFactory;
+ this.grainFactory = grainFactory;
}
public async virtual Task HandleAsync(CommandContext context, Func next)
@@ -36,11 +36,11 @@ namespace Squidex.Infrastructure.Commands
protected async Task ExecuteCommandAsync(TCommand typedCommand)
{
- var grain = await stateFactory.CreateAsync(typedCommand.AggregateId);
+ var grain = grainFactory.GetGrain(typedCommand.AggregateId);
var result = await grain.ExecuteAsync(typedCommand);
- return result;
+ return result.Value;
}
}
}
diff --git a/src/Squidex.Infrastructure/Commands/IDomainObjectGrain.cs b/src/Squidex.Infrastructure/Commands/IDomainObjectGrain.cs
index 37c80cdb6..500db0245 100644
--- a/src/Squidex.Infrastructure/Commands/IDomainObjectGrain.cs
+++ b/src/Squidex.Infrastructure/Commands/IDomainObjectGrain.cs
@@ -5,18 +5,16 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
-using System;
using System.Threading.Tasks;
-using Squidex.Infrastructure.States;
+using Orleans;
+using Squidex.Infrastructure.Orleans;
namespace Squidex.Infrastructure.Commands
{
- public interface IDomainObjectGrain : IStatefulObject
+ public interface IDomainObjectGrain : IGrainWithGuidKey
{
- Task ExecuteAsync(IAggregateCommand command);
-
Task WriteSnapshotAsync();
- long Version { get; }
+ Task> ExecuteAsync(J command);
}
-}
\ No newline at end of file
+}
diff --git a/src/Squidex.Infrastructure/Commands/SyncedGrainCommandMiddleware.cs b/src/Squidex.Infrastructure/Commands/SyncedGrainCommandMiddleware.cs
deleted file mode 100644
index d3d85159f..000000000
--- a/src/Squidex.Infrastructure/Commands/SyncedGrainCommandMiddleware.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-// ==========================================================================
-// Squidex Headless CMS
-// ==========================================================================
-// Copyright (c) Squidex UG (haftungsbeschraenkt)
-// All rights reserved. Licensed under the MIT license.
-// ==========================================================================
-
-using System;
-using System.Threading.Tasks;
-using Squidex.Infrastructure.States;
-using Squidex.Infrastructure.Tasks;
-
-namespace Squidex.Infrastructure.Commands
-{
- public class SyncedGrainCommandMiddleware : ICommandMiddleware where TCommand : IAggregateCommand where TGrain : IDomainObjectGrain
- {
- private readonly AsyncLockPool lockPool = new AsyncLockPool(10000);
- private readonly IStateFactory stateFactory;
-
- public SyncedGrainCommandMiddleware(IStateFactory stateFactory)
- {
- Guard.NotNull(stateFactory, nameof(stateFactory));
-
- this.stateFactory = stateFactory;
- }
-
- 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 id = typedCommand.AggregateId;
-
- using (await lockPool.LockAsync(typedCommand.AggregateId))
- {
- try
- {
- var grain = await stateFactory.GetSingleAsync(id);
-
- var result = await grain.ExecuteAsync(typedCommand);
-
- stateFactory.Synchronize(id);
-
- return result;
- }
- catch
- {
- stateFactory.Remove(id);
- throw;
- }
- }
- }
- }
-}
diff --git a/src/Squidex.Infrastructure/EventSourcing/DefaultEventNotifier.cs b/src/Squidex.Infrastructure/EventSourcing/DefaultEventNotifier.cs
deleted file mode 100644
index 1e94354e3..000000000
--- a/src/Squidex.Infrastructure/EventSourcing/DefaultEventNotifier.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-// ==========================================================================
-// Squidex Headless CMS
-// ==========================================================================
-// Copyright (c) Squidex UG (haftungsbeschränkt)
-// All rights reserved. Licensed under the MIT license.
-// ==========================================================================
-
-using System;
-
-namespace Squidex.Infrastructure.EventSourcing
-{
- public sealed class DefaultEventNotifier : IEventNotifier
- {
- private static readonly string ChannelName = typeof(DefaultEventNotifier).Name;
-
- private readonly IPubSub pubsub;
-
- public sealed class EventNotification
- {
- public string StreamName { get; set; }
- }
-
- public DefaultEventNotifier(IPubSub pubsub)
- {
- Guard.NotNull(pubsub, nameof(pubsub));
-
- this.pubsub = pubsub;
- }
-
- public void NotifyEventsStored(string streamName)
- {
- pubsub.Publish(new EventNotification { StreamName = streamName }, true);
- }
-
- public IDisposable Subscribe(Action handler)
- {
- return pubsub.Subscribe(x => handler?.Invoke(x.StreamName));
- }
- }
-}
diff --git a/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs b/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs
index e05a08c65..93580c7f0 100644
--- a/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs
+++ b/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs
@@ -8,25 +8,30 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
+using Orleans;
+using Orleans.Concurrency;
using Squidex.Infrastructure.Log;
+using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.EventSourcing.Grains
{
- public class EventConsumerGrain : DisposableObjectBase, IStatefulObject, IEventSubscriber
+ public class EventConsumerGrain : GrainOfString, IEventConsumerGrain
{
- private readonly IEventDataFormatter eventDataFormatter;
+ private readonly EventConsumerFactory eventConsumerFactory;
private readonly IStore store;
+ private readonly IEventDataFormatter eventDataFormatter;
private readonly IEventStore eventStore;
private readonly ISemanticLog log;
- private readonly SingleThreadedDispatcher dispatcher = new SingleThreadedDispatcher(1);
+ private TaskScheduler scheduler;
+ private IPersistence persistence;
private IEventSubscription currentSubscription;
private IEventConsumer eventConsumer;
- private IPersistence persistence;
private EventConsumerState state = new EventConsumerState();
public EventConsumerGrain(
+ EventConsumerFactory eventConsumerFactory,
IStore store,
IEventStore eventStore,
IEventDataFormatter eventDataFormatter,
@@ -36,95 +41,54 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
Guard.NotNull(store, nameof(store));
Guard.NotNull(eventStore, nameof(eventStore));
Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter));
+ Guard.NotNull(eventConsumerFactory, nameof(eventConsumerFactory));
this.log = log;
this.store = store;
this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter;
+ this.eventConsumerFactory = eventConsumerFactory;
}
- protected override void DisposeObject(bool disposing)
- {
- if (disposing)
- {
- dispatcher.StopAndWaitAsync().Wait();
- }
- }
-
- public Task ActivateAsync(string key)
+ public override Task OnActivateAsync(string key)
{
- persistence = store.WithSnapshots(key, s => state = s);
-
- return persistence.ReadAsync();
- }
+ scheduler = TaskScheduler.Current;
- protected virtual IEventSubscription CreateSubscription(IEventStore eventStore, string streamFilter, string position)
- {
- return new RetrySubscription(eventStore, this, streamFilter, position);
- }
+ eventConsumer = eventConsumerFactory(key);
- public virtual EventConsumerInfo GetState()
- {
- return state.ToInfo(this.eventConsumer.Name);
- }
+ persistence = store.WithSnapshots(GetType(), eventConsumer.Name, s => state = s);
- public virtual void Stop()
- {
- dispatcher.DispatchAsync(HandleStopAsync).Forget();
- }
-
- public virtual void Start()
- {
- dispatcher.DispatchAsync(HandleStartAsync).Forget();
+ return persistence.ReadAsync();
}
- public virtual void Reset()
+ public Task> GetStateAsync()
{
- dispatcher.DispatchAsync(HandleResetAsync).Forget();
+ return Task.FromResult(state.ToInfo(this.eventConsumer.Name).AsImmutable());
}
- public virtual void Activate(IEventConsumer eventConsumer)
+ public Task OnEventAsync(Immutable subscription, Immutable storedEvent)
{
- Guard.NotNull(eventConsumer, nameof(eventConsumer));
-
- dispatcher.DispatchAsync(() => HandleSetupAsync(eventConsumer)).Forget();
- }
-
- private Task HandleSetupAsync(IEventConsumer consumer)
- {
- eventConsumer = consumer;
-
- if (!state.IsStopped)
- {
- Subscribe(state.Position);
- }
-
- return TaskHelper.Done;
- }
-
- private Task HandleEventAsync(IEventSubscription subscription, StoredEvent storedEvent)
- {
- if (subscription != currentSubscription)
+ if (subscription.Value != currentSubscription)
{
return TaskHelper.Done;
}
return DoAndUpdateStateAsync(async () =>
{
- var @event = ParseKnownEvent(storedEvent);
+ var @event = ParseKnownEvent(storedEvent.Value);
if (@event != null)
{
await DispatchConsumerAsync(@event);
}
- state = state.Handled(storedEvent.EventPosition);
+ state = state.Handled(storedEvent.Value.EventPosition);
});
}
- private Task HandleErrorAsync(IEventSubscription subscription, Exception exception)
+ public Task OnErrorAsync(Immutable subscription, Immutable exception)
{
- if (subscription != currentSubscription)
+ if (subscription.Value != currentSubscription)
{
return TaskHelper.Done;
}
@@ -133,11 +97,21 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
{
Unsubscribe();
- state = state.Failed(exception);
+ state = state.Failed(exception.Value);
});
}
- private Task HandleStartAsync()
+ public Task ActivateAsync()
+ {
+ if (!state.IsStopped)
+ {
+ Subscribe(state.Position);
+ }
+
+ return TaskHelper.Done;
+ }
+
+ public Task StartAsync()
{
if (!state.IsStopped)
{
@@ -152,7 +126,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
});
}
- private Task HandleStopAsync()
+ public Task StopAsync()
{
if (state.IsStopped)
{
@@ -167,7 +141,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
});
}
- private Task HandleResetAsync()
+ public Task ResetAsync()
{
return DoAndUpdateStateAsync(async () =>
{
@@ -181,16 +155,6 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
});
}
- Task IEventSubscriber.OnEventAsync(IEventSubscription subscription, StoredEvent storedEvent)
- {
- return dispatcher.DispatchAsync(() => HandleEventAsync(subscription, storedEvent));
- }
-
- Task IEventSubscriber.OnErrorAsync(IEventSubscription subscription, Exception exception)
- {
- return dispatcher.DispatchAsync(() => HandleErrorAsync(subscription, exception));
- }
-
private Task DoAndUpdateStateAsync(Action action, [CallerMemberName] string caller = null)
{
return DoAndUpdateStateAsync(() => { action(); return TaskHelper.Done; }, caller);
@@ -283,7 +247,11 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
if (currentSubscription == null)
{
currentSubscription?.StopAsync().Forget();
- currentSubscription = CreateSubscription(eventStore, eventConsumer.EventsFilter, position);
+ currentSubscription = CreateSubscription(eventConsumer.EventsFilter, position);
+ }
+ else
+ {
+ currentSubscription.WakeUp();
}
}
@@ -305,5 +273,20 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
return null;
}
}
+
+ protected virtual IEventConsumerGrain GetSelf()
+ {
+ return this.AsReference();
+ }
+
+ protected virtual IEventSubscription CreateSubscription(IEventStore store, IEventSubscriber subscriber, string streamFilter, string position)
+ {
+ return new RetrySubscription(store, subscriber, streamFilter, position);
+ }
+
+ private IEventSubscription CreateSubscription(string streamFilter, string position)
+ {
+ return CreateSubscription(eventStore, new WrapperSubscription(GetSelf(), scheduler), streamFilter, position);
+ }
}
}
\ No newline at end of file
diff --git a/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrainManager.cs b/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrainManager.cs
deleted file mode 100644
index e833aa5b4..000000000
--- a/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrainManager.cs
+++ /dev/null
@@ -1,90 +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 System.Linq;
-using System.Threading.Tasks;
-using Squidex.Infrastructure.EventSourcing.Grains.Messages;
-using Squidex.Infrastructure.States;
-
-namespace Squidex.Infrastructure.EventSourcing.Grains
-{
- public sealed class EventConsumerGrainManager : DisposableObjectBase, IRunnable
- {
- private readonly IStateFactory factory;
- private readonly IPubSub pubSub;
- private readonly List consumers;
- private readonly List subscriptions = new List();
-
- public EventConsumerGrainManager(IEnumerable consumers, IPubSub pubSub, IStateFactory factory)
- {
- Guard.NotNull(pubSub, nameof(pubSub));
- Guard.NotNull(factory, nameof(factory));
- Guard.NotNull(consumers, nameof(consumers));
-
- this.pubSub = pubSub;
- this.factory = factory;
- this.consumers = consumers.ToList();
- }
-
- public void Run()
- {
- var actors = new Dictionary();
-
- foreach (var consumer in consumers)
- {
- var actor = factory.CreateAsync(consumer.Name).Result;
-
- actors[consumer.Name] = actor;
- actor.Activate(consumer);
- }
-
- subscriptions.Add(pubSub.Subscribe(m =>
- {
- if (actors.TryGetValue(m.ConsumerName, out var actor))
- {
- actor.Start();
- }
- }));
-
- subscriptions.Add(pubSub.Subscribe(m =>
- {
- if (actors.TryGetValue(m.ConsumerName, out var actor))
- {
- actor.Stop();
- }
- }));
-
- subscriptions.Add(pubSub.Subscribe(m =>
- {
- if (actors.TryGetValue(m.ConsumerName, out var actor))
- {
- actor.Reset();
- }
- }));
-
- subscriptions.Add(pubSub.ReceiveAsync(request =>
- {
- var states = actors.Values.Select(x => x.GetState()).ToArray();
-
- return Task.FromResult(new GetStatesResponse { States = states });
- }));
- }
-
- protected override void DisposeObject(bool disposing)
- {
- if (disposing)
- {
- foreach (var subscription in subscriptions)
- {
- subscription.Dispose();
- }
- }
- }
- }
-}
diff --git a/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerManagerGrain.cs b/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerManagerGrain.cs
new file mode 100644
index 000000000..97b3e93ca
--- /dev/null
+++ b/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerManagerGrain.cs
@@ -0,0 +1,104 @@
+// ==========================================================================
+// 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.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Orleans;
+using Orleans.Concurrency;
+using Orleans.Core;
+using Orleans.Runtime;
+
+namespace Squidex.Infrastructure.EventSourcing.Grains
+{
+ public class EventConsumerManagerGrain : Grain, IEventConsumerManagerGrain, IRemindable
+ {
+ private readonly IEnumerable eventConsumers;
+
+ public EventConsumerManagerGrain(IEnumerable eventConsumers)
+ : this(eventConsumers, null, null)
+ {
+ }
+
+ protected EventConsumerManagerGrain(
+ IEnumerable eventConsumers,
+ IGrainIdentity identity,
+ IGrainRuntime runtime)
+ : base(identity, runtime)
+ {
+ Guard.NotNull(eventConsumers, nameof(eventConsumers));
+
+ this.eventConsumers = eventConsumers;
+ }
+
+ public override Task OnActivateAsync()
+ {
+ DelayDeactivation(TimeSpan.FromDays(1));
+
+ RegisterOrUpdateReminder("Default", TimeSpan.Zero, TimeSpan.FromMinutes(10));
+ RegisterTimer(x => ActivateAsync(null), null, TimeSpan.Zero, TimeSpan.FromSeconds(10));
+
+ return Task.FromResult(true);
+ }
+
+ public Task ActivateAsync(string streamName)
+ {
+ var tasks =
+ eventConsumers
+ .Where(c => streamName == null || Regex.IsMatch(streamName, c.EventsFilter))
+ .Select(c => GrainFactory.GetGrain(c.Name))
+ .Select(c => c.ActivateAsync());
+
+ return Task.WhenAll(tasks);
+ }
+
+ public async Task>> GetConsumersAsync()
+ {
+ var tasks =
+ eventConsumers
+ .Select(c => GrainFactory.GetGrain(c.Name))
+ .Select(c => c.GetStateAsync());
+
+ var consumerInfos = await Task.WhenAll(tasks);
+
+ return new Immutable>(consumerInfos.Select(r => r.Value).ToList());
+ }
+
+ public Task ResetAsync(string consumerName)
+ {
+ var eventConsumer = GrainFactory.GetGrain(consumerName);
+
+ return eventConsumer.ResetAsync();
+ }
+
+ public Task StartAsync(string consumerName)
+ {
+ var eventConsumer = GrainFactory.GetGrain(consumerName);
+
+ return eventConsumer.StartAsync();
+ }
+
+ public Task StopAsync(string consumerName)
+ {
+ var eventConsumer = GrainFactory.GetGrain(consumerName);
+
+ return eventConsumer.StopAsync();
+ }
+
+ public Task ActivateAsync()
+ {
+ return ActivateAsync(null);
+ }
+
+ public Task ReceiveReminder(string reminderName, TickStatus status)
+ {
+ return ActivateAsync(null);
+ }
+ }
+}
diff --git a/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerGrain.cs b/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerGrain.cs
new file mode 100644
index 000000000..58b7bf2fb
--- /dev/null
+++ b/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerGrain.cs
@@ -0,0 +1,29 @@
+// ==========================================================================
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex UG (haftungsbeschränkt)
+// All rights reserved. Licensed under the MIT license.
+// ==========================================================================
+
+using System;
+using System.Threading.Tasks;
+using Orleans.Concurrency;
+using Squidex.Infrastructure.Orleans;
+
+namespace Squidex.Infrastructure.EventSourcing.Grains
+{
+ public interface IEventConsumerGrain : IBackgroundGrain
+ {
+ Task> GetStateAsync();
+
+ Task StopAsync();
+
+ Task StartAsync();
+
+ Task ResetAsync();
+
+ Task OnEventAsync(Immutable subscription, Immutable storedEvent);
+
+ Task OnErrorAsync(Immutable subscription, Immutable exception);
+ }
+}
diff --git a/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerManagerGrain.cs b/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerManagerGrain.cs
new file mode 100644
index 000000000..1730b7b30
--- /dev/null
+++ b/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerManagerGrain.cs
@@ -0,0 +1,27 @@
+// ==========================================================================
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex UG (haftungsbeschränkt)
+// All rights reserved. Licensed under the MIT license.
+// ==========================================================================
+
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Orleans.Concurrency;
+using Squidex.Infrastructure.Orleans;
+
+namespace Squidex.Infrastructure.EventSourcing.Grains
+{
+ public interface IEventConsumerManagerGrain : IBackgroundGrain
+ {
+ Task ActivateAsync(string streamName);
+
+ Task StopAsync(string consumerName);
+
+ Task StartAsync(string consumerName);
+
+ Task ResetAsync(string consumerName);
+
+ Task>> GetConsumersAsync();
+ }
+}
diff --git a/src/Squidex.Infrastructure/EventSourcing/Grains/OrleansEventNotifier.cs b/src/Squidex.Infrastructure/EventSourcing/Grains/OrleansEventNotifier.cs
new file mode 100644
index 000000000..0e97746e4
--- /dev/null
+++ b/src/Squidex.Infrastructure/EventSourcing/Grains/OrleansEventNotifier.cs
@@ -0,0 +1,40 @@
+// ==========================================================================
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex UG (haftungsbeschränkt)
+// All rights reserved. Licensed under the MIT license.
+// ==========================================================================
+
+using System;
+using Orleans;
+
+namespace Squidex.Infrastructure.EventSourcing.Grains
+{
+ public sealed class OrleansEventNotifier : IEventNotifier, IInitializable
+ {
+ private readonly IGrainFactory factory;
+ private IEventConsumerManagerGrain eventConsumerManagerGrain;
+
+ public OrleansEventNotifier(IGrainFactory factory)
+ {
+ Guard.NotNull(factory, nameof(factory));
+
+ this.factory = factory;
+ }
+
+ public void Initialize()
+ {
+ eventConsumerManagerGrain = factory.GetGrain("Default");
+ }
+
+ public void NotifyEventsStored(string streamName)
+ {
+ eventConsumerManagerGrain?.ActivateAsync(streamName);
+ }
+
+ public IDisposable Subscribe(Action handler)
+ {
+ return null;
+ }
+ }
+}
diff --git a/src/Squidex.Infrastructure/EventSourcing/Grains/WrapperSubscription.cs b/src/Squidex.Infrastructure/EventSourcing/Grains/WrapperSubscription.cs
new file mode 100644
index 000000000..012179f1f
--- /dev/null
+++ b/src/Squidex.Infrastructure/EventSourcing/Grains/WrapperSubscription.cs
@@ -0,0 +1,42 @@
+// ==========================================================================
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex UG (haftungsbeschränkt)
+// All rights reserved. Licensed under the MIT license.
+// ==========================================================================
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Orleans.Concurrency;
+
+namespace Squidex.Infrastructure.EventSourcing.Grains
+{
+ internal sealed class WrapperSubscription : IEventSubscriber
+ {
+ private readonly IEventConsumerGrain grain;
+ private readonly TaskScheduler scheduler;
+
+ public WrapperSubscription(IEventConsumerGrain grain, TaskScheduler scheduler)
+ {
+ this.grain = grain;
+
+ this.scheduler = scheduler ?? TaskScheduler.Default;
+ }
+
+ public Task OnEventAsync(IEventSubscription subscription, StoredEvent storedEvent)
+ {
+ return Dispatch(() => grain.OnEventAsync(subscription.AsImmutable(), storedEvent.AsImmutable()));
+ }
+
+ public Task OnErrorAsync(IEventSubscription subscription, Exception exception)
+ {
+ return Dispatch(() => grain.OnErrorAsync(subscription.AsImmutable(), exception.AsImmutable()));
+ }
+
+ private Task Dispatch(Func task)
+ {
+ return Task.Factory.StartNew(() => task(), CancellationToken.None, TaskCreationOptions.None, scheduler).Unwrap();
+ }
+ }
+}
diff --git a/src/Squidex.Infrastructure/EventSourcing/IEventNotifier.cs b/src/Squidex.Infrastructure/EventSourcing/IEventNotifier.cs
index ce28bb491..6e5bbe94f 100644
--- a/src/Squidex.Infrastructure/EventSourcing/IEventNotifier.cs
+++ b/src/Squidex.Infrastructure/EventSourcing/IEventNotifier.cs
@@ -5,14 +5,10 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
-using System;
-
namespace Squidex.Infrastructure.EventSourcing
{
public interface IEventNotifier
{
void NotifyEventsStored(string streamName);
-
- IDisposable Subscribe(Action handler);
}
}
diff --git a/src/Squidex.Infrastructure/EventSourcing/IEventSubscription.cs b/src/Squidex.Infrastructure/EventSourcing/IEventSubscription.cs
index a33b1f22b..48ead1da9 100644
--- a/src/Squidex.Infrastructure/EventSourcing/IEventSubscription.cs
+++ b/src/Squidex.Infrastructure/EventSourcing/IEventSubscription.cs
@@ -11,6 +11,8 @@ namespace Squidex.Infrastructure.EventSourcing
{
public interface IEventSubscription
{
+ void WakeUp();
+
Task StopAsync();
}
}
\ No newline at end of file
diff --git a/src/Squidex.Infrastructure/EventSourcing/PollingSubscription.cs b/src/Squidex.Infrastructure/EventSourcing/PollingSubscription.cs
index 7cbb556b9..10c908ad5 100644
--- a/src/Squidex.Infrastructure/EventSourcing/PollingSubscription.cs
+++ b/src/Squidex.Infrastructure/EventSourcing/PollingSubscription.cs
@@ -14,10 +14,8 @@ namespace Squidex.Infrastructure.EventSourcing
{
public sealed class PollingSubscription : IEventSubscription
{
- private readonly IEventNotifier eventNotifier;
private readonly IEventStore eventStore;
private readonly IEventSubscriber eventSubscriber;
- private readonly IDisposable notification;
private readonly CompletionTimer timer;
private readonly Regex streamRegex;
private readonly string streamFilter;
@@ -25,17 +23,14 @@ namespace Squidex.Infrastructure.EventSourcing
public PollingSubscription(
IEventStore eventStore,
- IEventNotifier eventNotifier,
IEventSubscriber eventSubscriber,
string streamFilter,
string position)
{
Guard.NotNull(eventStore, nameof(eventStore));
- Guard.NotNull(eventNotifier, nameof(eventNotifier));
Guard.NotNull(eventSubscriber, nameof(eventSubscriber));
this.position = position;
- this.eventNotifier = eventNotifier;
this.eventStore = eventStore;
this.eventSubscriber = eventSubscriber;
this.streamFilter = streamFilter;
@@ -61,20 +56,15 @@ namespace Squidex.Infrastructure.EventSourcing
}
}
});
+ }
- notification = eventNotifier.Subscribe(streamName =>
- {
- if (streamRegex.IsMatch(streamName))
- {
- timer.SkipCurrentDelay();
- }
- });
+ public void WakeUp()
+ {
+ timer.SkipCurrentDelay();
}
public Task StopAsync()
{
- notification?.Dispose();
-
return timer.StopAsync();
}
}
diff --git a/src/Squidex.Infrastructure/EventSourcing/RetrySubscription.cs b/src/Squidex.Infrastructure/EventSourcing/RetrySubscription.cs
index d023eead5..60a9f5679 100644
--- a/src/Squidex.Infrastructure/EventSourcing/RetrySubscription.cs
+++ b/src/Squidex.Infrastructure/EventSourcing/RetrySubscription.cs
@@ -57,6 +57,11 @@ namespace Squidex.Infrastructure.EventSourcing
currentSubscription = null;
}
+ public void WakeUp()
+ {
+ currentSubscription?.WakeUp();
+ }
+
private async Task HandleEventAsync(IEventSubscription subscription, StoredEvent storedEvent)
{
if (subscription == currentSubscription)
diff --git a/src/Squidex.Infrastructure/Orleans/Bootstrap.cs b/src/Squidex.Infrastructure/Orleans/Bootstrap.cs
new file mode 100644
index 000000000..79abd18b9
--- /dev/null
+++ b/src/Squidex.Infrastructure/Orleans/Bootstrap.cs
@@ -0,0 +1,33 @@
+// ==========================================================================
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex UG (haftungsbeschraenkt)
+// All rights reserved. Licensed under the MIT license.
+// ==========================================================================
+
+using System.Threading;
+using System.Threading.Tasks;
+using Orleans;
+using Orleans.Runtime;
+
+namespace Squidex.Infrastructure.Orleans
+{
+ public sealed class Bootstrap : IStartupTask where T : IBackgroundGrain
+ {
+ private readonly IGrainFactory grainFactory;
+
+ public Bootstrap(IGrainFactory grainFactory)
+ {
+ Guard.NotNull(grainFactory, nameof(grainFactory));
+
+ this.grainFactory = grainFactory;
+ }
+
+ public Task Execute(CancellationToken cancellationToken)
+ {
+ var grain = grainFactory.GetGrain("Default");
+
+ return grain.ActivateAsync();
+ }
+ }
+}
diff --git a/src/Squidex.Infrastructure/Orleans/GrainOfGuid.cs b/src/Squidex.Infrastructure/Orleans/GrainOfGuid.cs
new file mode 100644
index 000000000..b014a9c63
--- /dev/null
+++ b/src/Squidex.Infrastructure/Orleans/GrainOfGuid.cs
@@ -0,0 +1,27 @@
+// ==========================================================================
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex UG (haftungsbeschraenkt)
+// All rights reserved. Licensed under the MIT license.
+// ==========================================================================
+
+using System;
+using System.Threading.Tasks;
+using Orleans;
+using Squidex.Infrastructure.Tasks;
+
+namespace Squidex.Infrastructure.Orleans
+{
+ public abstract class GrainOfGuid : Grain
+ {
+ public override Task OnActivateAsync()
+ {
+ return OnActivateAsync(this.GetPrimaryKey());
+ }
+
+ public virtual Task OnActivateAsync(Guid key)
+ {
+ return TaskHelper.Done;
+ }
+ }
+}
diff --git a/src/Squidex.Infrastructure/Orleans/GrainOfString.cs b/src/Squidex.Infrastructure/Orleans/GrainOfString.cs
new file mode 100644
index 000000000..4ed33bacc
--- /dev/null
+++ b/src/Squidex.Infrastructure/Orleans/GrainOfString.cs
@@ -0,0 +1,26 @@
+// ==========================================================================
+// 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.Tasks;
+
+namespace Squidex.Infrastructure.Orleans
+{
+ public abstract class GrainOfString : Grain
+ {
+ public override Task OnActivateAsync()
+ {
+ return OnActivateAsync(this.GetPrimaryKeyString());
+ }
+
+ public virtual Task OnActivateAsync(string key)
+ {
+ return TaskHelper.Done;
+ }
+ }
+}
diff --git a/src/Squidex.Infrastructure/States/IStatefulObject.cs b/src/Squidex.Infrastructure/Orleans/IBackgroundGrain.cs
similarity index 65%
rename from src/Squidex.Infrastructure/States/IStatefulObject.cs
rename to src/Squidex.Infrastructure/Orleans/IBackgroundGrain.cs
index 45769b2ce..e30295b8a 100644
--- a/src/Squidex.Infrastructure/States/IStatefulObject.cs
+++ b/src/Squidex.Infrastructure/Orleans/IBackgroundGrain.cs
@@ -1,16 +1,17 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
-// Copyright (c) Squidex UG (haftungsbeschränkt)
+// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
+using Orleans;
-namespace Squidex.Infrastructure.States
+namespace Squidex.Infrastructure.Orleans
{
- public interface IStatefulObject
+ public interface IBackgroundGrain : IGrainWithStringKey
{
- Task ActivateAsync(TKey key);
+ Task ActivateAsync();
}
}
diff --git a/src/Squidex.Infrastructure/EventSourcing/Grains/Messages/StartConsumerMessage.cs b/src/Squidex.Infrastructure/Orleans/J.cs
similarity index 55%
rename from src/Squidex.Infrastructure/EventSourcing/Grains/Messages/StartConsumerMessage.cs
rename to src/Squidex.Infrastructure/Orleans/J.cs
index 8d8378653..cd1190790 100644
--- a/src/Squidex.Infrastructure/EventSourcing/Grains/Messages/StartConsumerMessage.cs
+++ b/src/Squidex.Infrastructure/Orleans/J.cs
@@ -1,14 +1,18 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
-// Copyright (c) Squidex UG (haftungsbeschränkt)
+// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
-namespace Squidex.Infrastructure.EventSourcing.Grains.Messages
+using Newtonsoft.Json;
+
+#pragma warning disable SA1401 // Fields must be private
+
+namespace Squidex.Infrastructure.Orleans
{
- public sealed class StartConsumerMessage
+ public static class J
{
- public string ConsumerName { get; set; }
+ public static JsonSerializer Serializer = new JsonSerializer();
}
}
diff --git a/src/Squidex.Infrastructure/EventSourcing/Grains/Messages/GetStatesResponse.cs b/src/Squidex.Infrastructure/Orleans/JExtensions.cs
similarity index 59%
rename from src/Squidex.Infrastructure/EventSourcing/Grains/Messages/GetStatesResponse.cs
rename to src/Squidex.Infrastructure/Orleans/JExtensions.cs
index 922116d82..49125b346 100644
--- a/src/Squidex.Infrastructure/EventSourcing/Grains/Messages/GetStatesResponse.cs
+++ b/src/Squidex.Infrastructure/Orleans/JExtensions.cs
@@ -1,14 +1,17 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
-// Copyright (c) Squidex UG (haftungsbeschränkt)
+// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
-namespace Squidex.Infrastructure.EventSourcing.Grains.Messages
+namespace Squidex.Infrastructure.Orleans
{
- public sealed class GetStatesResponse
+ public static class JExtensions
{
- public EventConsumerInfo[] States { get; set; }
+ 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..c6f28cd45
--- /dev/null
+++ b/src/Squidex.Infrastructure/Orleans/J{T}.cs
@@ -0,0 +1,90 @@
+// ==========================================================================
+// 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 override string ToString()
+ {
+ return value?.ToString() ?? string.Empty;
+ }
+
+ public static Task> AsTask(T value)
+ {
+ return Task.FromResult>(value);
+ }
+
+ [CopierMethod]
+ public static object Copy(object input, ICopyContext context)
+ {
+ return input;
+ }
+
+ [SerializerMethod]
+ public 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]
+ public 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.Infrastructure/Squidex.Infrastructure.csproj b/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
index ba723caf1..77029bf52 100644
--- a/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
+++ b/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
@@ -8,10 +8,13 @@
True
-
-
-
-
+
+
+
+
+
+
+
diff --git a/src/Squidex.Infrastructure/States/IStateFactory.cs b/src/Squidex.Infrastructure/States/IStateFactory.cs
deleted file mode 100644
index cc10879ec..000000000
--- a/src/Squidex.Infrastructure/States/IStateFactory.cs
+++ /dev/null
@@ -1,31 +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;
-
-namespace Squidex.Infrastructure.States
-{
- public interface IStateFactory
- {
- Task GetSingleAsync(string key) where T : IStatefulObject;
-
- Task GetSingleAsync(Guid key) where T : IStatefulObject;
-
- Task GetSingleAsync(TKey key) where T : IStatefulObject;
-
- Task CreateAsync(string key) where T : IStatefulObject;
-
- Task CreateAsync(Guid key) where T : IStatefulObject;
-
- Task CreateAsync(TKey key) where T : IStatefulObject;
-
- void Remove(TKey key) where T : IStatefulObject;
-
- void Synchronize(TKey key) where T : IStatefulObject;
- }
-}
diff --git a/src/Squidex.Infrastructure/States/StateFactory.cs b/src/Squidex.Infrastructure/States/StateFactory.cs
deleted file mode 100644
index 6e196beba..000000000
--- a/src/Squidex.Infrastructure/States/StateFactory.cs
+++ /dev/null
@@ -1,140 +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 Microsoft.Extensions.Caching.Memory;
-
-#pragma warning disable RECS0096 // Type parameter is never used
-
-namespace Squidex.Infrastructure.States
-{
- public sealed class StateFactory : DisposableObjectBase, IInitializable, IStateFactory
- {
- private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(10);
- private readonly IPubSub pubSub;
- private readonly IMemoryCache statesCache;
- private readonly IServiceProvider services;
- private readonly object lockObject = new object();
- private IDisposable pubSubSubscription;
-
- public sealed class ObjectHolder where T : IStatefulObject
- {
- private readonly Task activationTask;
- private readonly T obj;
-
- public ObjectHolder(T obj, TKey key)
- {
- this.obj = obj;
-
- activationTask = obj.ActivateAsync(key);
- }
-
- public async Task ActivateAsync()
- {
- await activationTask;
-
- return obj;
- }
- }
-
- public StateFactory(IPubSub pubSub, IMemoryCache statesCache, IServiceProvider services)
- {
- Guard.NotNull(pubSub, nameof(pubSub));
- Guard.NotNull(services, nameof(services));
- Guard.NotNull(statesCache, nameof(statesCache));
-
- this.pubSub = pubSub;
- this.services = services;
- this.statesCache = statesCache;
- }
-
- public void Initialize()
- {
- pubSubSubscription = pubSub.Subscribe(m =>
- {
- lock (lockObject)
- {
- statesCache.Remove(m.Key);
- }
- });
- }
-
- public Task CreateAsync(string key) where T : IStatefulObject
- {
- return CreateAsync(key);
- }
-
- public Task CreateAsync(Guid key) where T : IStatefulObject
- {
- return CreateAsync(key);
- }
-
- public async Task CreateAsync(TKey key) where T : IStatefulObject
- {
- Guard.NotNull(key, nameof(key));
-
- var state = (T)services.GetService(typeof(T));
-
- await state.ActivateAsync(key);
-
- return state;
- }
-
- public Task GetSingleAsync(string key) where T : IStatefulObject
- {
- return GetSingleAsync(key);
- }
-
- public Task GetSingleAsync(Guid key) where T : IStatefulObject
- {
- return GetSingleAsync(key);
- }
-
- public Task GetSingleAsync(TKey key) where T : IStatefulObject
- {
- Guard.NotNull(key, nameof(key));
-
- lock (lockObject)
- {
- if (statesCache.TryGetValue>(key, out var stateObj))
- {
- return stateObj.ActivateAsync();
- }
-
- var state = (T)services.GetService(typeof(T));
-
- stateObj = new ObjectHolder(state, key);
-
- statesCache.CreateEntry(key)
- .SetValue(stateObj)
- .SetAbsoluteExpiration(CacheDuration)
- .Dispose();
-
- return stateObj.ActivateAsync();
- }
- }
-
- public void Remove(TKey key) where T : IStatefulObject
- {
- statesCache.Remove(key);
- }
-
- public void Synchronize(TKey key) where T : IStatefulObject
- {
- pubSub.Publish(new InvalidateMessage { Key = key.ToString() }, false);
- }
-
- protected override void DisposeObject(bool disposing)
- {
- if (disposing && pubSubSubscription != null)
- {
- pubSubSubscription.Dispose();
- }
- }
- }
-}
diff --git a/src/Squidex.Infrastructure/Tasks/PartitionedActionBlock.cs b/src/Squidex.Infrastructure/Tasks/PartitionedActionBlock.cs
index 16fd0c779..a649ce53f 100644
--- a/src/Squidex.Infrastructure/Tasks/PartitionedActionBlock.cs
+++ b/src/Squidex.Infrastructure/Tasks/PartitionedActionBlock.cs
@@ -24,7 +24,7 @@ namespace Squidex.Infrastructure.Tasks
}
public PartitionedActionBlock(Action action, Func partitioner)
- : this (ToAsync(action), partitioner, new ExecutionDataflowBlockOptions())
+ : this (action?.ToAsync(), partitioner, new ExecutionDataflowBlockOptions())
{
}
@@ -34,7 +34,7 @@ namespace Squidex.Infrastructure.Tasks
}
public PartitionedActionBlock(Action action, Func partitioner, ExecutionDataflowBlockOptions dataflowBlockOptions)
- : this(ToAsync(action), partitioner, dataflowBlockOptions)
+ : this(action?.ToAsync(), partitioner, dataflowBlockOptions)
{
}
@@ -94,17 +94,5 @@ namespace Squidex.Infrastructure.Tasks
{
distributor.Fault(exception);
}
-
- private static Func ToAsync(Action action)
- {
- Guard.NotNull(action, nameof(action));
-
- return x =>
- {
- action(x);
-
- return TaskHelper.Done;
- };
- }
}
}
diff --git a/src/Squidex/AppConfiguration.cs b/src/Squidex/AppConfiguration.cs
deleted file mode 100644
index 6c707bafb..000000000
--- a/src/Squidex/AppConfiguration.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-// ==========================================================================
-// Squidex Headless CMS
-// ==========================================================================
-// Copyright (c) Squidex UG (haftungsbeschränkt)
-// All rights reserved. Licensed under the MIT license.
-// ==========================================================================
-
-using Microsoft.Extensions.Configuration;
-
-namespace Squidex
-{
- public static class AppConfiguration
- {
- public static void AddAppConfiguration(this IConfigurationBuilder builder, string environmentName, string[] args)
- {
- builder.Sources.Clear();
-
- builder.AddJsonFile("appsettings.json", true, true);
- builder.AddJsonFile($"appsettings.{environmentName}.json", true);
-
- builder.AddEnvironmentVariables();
-
- builder.AddCommandLine(args);
- }
- }
-}
diff --git a/src/Squidex/AppServices.cs b/src/Squidex/AppServices.cs
index baa614181..3a6e0e91f 100644
--- a/src/Squidex/AppServices.cs
+++ b/src/Squidex/AppServices.cs
@@ -26,17 +26,19 @@ namespace Squidex
services.AddMyAssetServices(config);
services.AddMyAuthentication(config);
+ services.AddMyEntitiesServices(config);
services.AddMyEventPublishersServices(config);
services.AddMyEventStoreServices(config);
services.AddMyIdentityServer();
- services.AddMyInfrastructureServices(config);
+ services.AddMyInfrastructureServices();
+ services.AddMyLoggingServices(config);
+ services.AddMyMigrationServices();
services.AddMyMvc();
- services.AddMyPubSubServices(config);
- services.AddMyReadServices(config);
+ services.AddMyRuleServices();
services.AddMySerializers();
services.AddMyStoreServices(config);
services.AddMySwaggerSettings();
- services.AddMyWriteServices();
+ services.AddMySubscriptionServices(config);
services.Configure(
config.GetSection("urls"));
diff --git a/src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs b/src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs
index 41e232307..6dee278b9 100644
--- a/src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs
+++ b/src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs
@@ -5,15 +5,14 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
-using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using NSwag.Annotations;
+using Orleans;
using Squidex.Areas.Api.Controllers.EventConsumers.Models;
-using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
-using Squidex.Infrastructure.EventSourcing.Grains.Messages;
+using Squidex.Infrastructure.EventSourcing.Grains;
using Squidex.Infrastructure.Reflection;
using Squidex.Pipeline;
@@ -25,12 +24,12 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers
[SwaggerIgnore]
public sealed class EventConsumersController : ApiController
{
- private readonly IPubSub pubSub;
+ private readonly IEventConsumerManagerGrain eventConsumerManagerGrain;
- public EventConsumersController(ICommandBus commandBus, IPubSub pubSub)
+ public EventConsumersController(ICommandBus commandBus, IGrainFactory grainFactory)
: base(commandBus)
{
- this.pubSub = pubSub;
+ eventConsumerManagerGrain = grainFactory.GetGrain("Default");
}
[HttpGet]
@@ -38,9 +37,9 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers
[ApiCosts(0)]
public async Task GetEventConsumers()
{
- var entities = await pubSub.RequestAsync(new GetStatesRequest(), TimeSpan.FromSeconds(2), true);
+ var entities = await eventConsumerManagerGrain.GetConsumersAsync();
- var models = entities.States.Select(x => SimpleMapper.Map(x, new EventConsumerDto())).ToList();
+ var models = entities.Value.Select(x => SimpleMapper.Map(x, new EventConsumerDto())).ToList();
return Ok(models);
}
@@ -48,9 +47,9 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers
[HttpPut]
[Route("event-consumers/{name}/start/")]
[ApiCosts(0)]
- public IActionResult Start(string name)
+ public async Task Start(string name)
{
- pubSub.Publish(new StartConsumerMessage { ConsumerName = name }, true);
+ await eventConsumerManagerGrain.StartAsync(name);
return NoContent();
}
@@ -58,9 +57,9 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers
[HttpPut]
[Route("event-consumers/{name}/stop/")]
[ApiCosts(0)]
- public IActionResult Stop(string name)
+ public async Task Stop(string name)
{
- pubSub.Publish(new StopConsumerMessage { ConsumerName = name }, true);
+ await eventConsumerManagerGrain.StopAsync(name);
return NoContent();
}
@@ -68,9 +67,9 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers
[HttpPut]
[Route("event-consumers/{name}/reset/")]
[ApiCosts(0)]
- public IActionResult Reset(string name)
+ public async Task Reset(string name)
{
- pubSub.Publish(new ResetConsumerMessage { ConsumerName = name }, true);
+ await eventConsumerManagerGrain.ResetAsync(name);
return NoContent();
}
diff --git a/src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs b/src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs
index 24b5e1e93..aeea54e9c 100644
--- a/src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs
+++ b/src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs
@@ -130,7 +130,8 @@ namespace Squidex.Areas.IdentityServer.Config
ClientSecrets = new List { new Secret(Constants.InternalClientSecret) },
RedirectUris = new List
{
- urlsOptions.BuildUrl($"{Constants.PortalPrefix}/signin-oidc", false)
+ urlsOptions.BuildUrl($"{Constants.PortalPrefix}/signin-oidc", false),
+ urlsOptions.BuildUrl($"{Constants.OrleansPrefix}/signin-oidc", false)
},
AccessTokenLifetime = (int)TimeSpan.FromDays(30).TotalSeconds,
AllowedGrantTypes = GrantTypes.ImplicitAndClientCredentials,
diff --git a/src/Squidex/Areas/OrleansDashboard/Middlewares/OrleansDashboardAuthenticationMiddleware.cs b/src/Squidex/Areas/OrleansDashboard/Middlewares/OrleansDashboardAuthenticationMiddleware.cs
new file mode 100644
index 000000000..db67abdd3
--- /dev/null
+++ b/src/Squidex/Areas/OrleansDashboard/Middlewares/OrleansDashboardAuthenticationMiddleware.cs
@@ -0,0 +1,43 @@
+// ==========================================================================
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex UG (haftungsbeschränkt)
+// All rights reserved. Licensed under the MIT license.
+// ==========================================================================
+
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.Cookies;
+using Microsoft.AspNetCore.Authentication.OpenIdConnect;
+using Microsoft.AspNetCore.Http;
+using Squidex.Shared.Identity;
+
+namespace Squidex.Areas.OrleansDashboard.Middlewares
+{
+ public sealed class OrleansDashboardAuthenticationMiddleware
+ {
+ private readonly RequestDelegate next;
+
+ public OrleansDashboardAuthenticationMiddleware(RequestDelegate next)
+ {
+ this.next = next;
+ }
+
+ public async Task Invoke(HttpContext context)
+ {
+ var authentication = await context.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
+
+ if (!authentication.Succeeded || !authentication.Principal.IsInRole(SquidexRoles.Administrator))
+ {
+ await context.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties
+ {
+ RedirectUri = context.Request.PathBase + context.Request.Path
+ });
+ }
+ else
+ {
+ await next(context);
+ }
+ }
+ }
+}
diff --git a/src/Squidex/Areas/OrleansDashboard/Startup.cs b/src/Squidex/Areas/OrleansDashboard/Startup.cs
new file mode 100644
index 000000000..d8b5da6f8
--- /dev/null
+++ b/src/Squidex/Areas/OrleansDashboard/Startup.cs
@@ -0,0 +1,27 @@
+// ==========================================================================
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex UG (haftungsbeschränkt)
+// All rights reserved. Licensed under the MIT license.
+// ==========================================================================
+
+using Microsoft.AspNetCore.Builder;
+using Orleans;
+using Squidex.Areas.OrleansDashboard.Middlewares;
+using Squidex.Config;
+
+namespace Squidex.Areas.OrleansDashboard
+{
+ public static class Startup
+ {
+ public static void ConfigureOrleansDashboard(this IApplicationBuilder app)
+ {
+ app.Map(Constants.OrleansPrefix, orleansApp =>
+ {
+ orleansApp.UseAuthentication();
+ orleansApp.UseMiddleware();
+ orleansApp.UseOrleansDashboard();
+ });
+ }
+ }
+}
diff --git a/src/Squidex/Config/Constants.cs b/src/Squidex/Config/Constants.cs
index 9c97c3d20..bd08faef8 100644
--- a/src/Squidex/Config/Constants.cs
+++ b/src/Squidex/Config/Constants.cs
@@ -17,6 +17,8 @@ namespace Squidex.Config
public static readonly string ApiScope = "squidex-api";
+ public static readonly string OrleansPrefix = "/orleans";
+
public static readonly string PortalPrefix = "/portal";
public static readonly string RoleScope = "role";
diff --git a/src/Squidex/Config/Domain/AssetServices.cs b/src/Squidex/Config/Domain/AssetServices.cs
index d2579c2ca..59297c8b6 100644
--- a/src/Squidex/Config/Domain/AssetServices.cs
+++ b/src/Squidex/Config/Domain/AssetServices.cs
@@ -9,6 +9,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Assets;
+using Squidex.Infrastructure.Assets.ImageSharp;
using Squidex.Infrastructure.Log;
namespace Squidex.Config.Domain
@@ -45,6 +46,9 @@ namespace Squidex.Config.Domain
.As();
}
});
+
+ services.AddSingletonAs()
+ .As();
}
}
}
diff --git a/src/Squidex/Config/Domain/WriteServices.cs b/src/Squidex/Config/Domain/EntitiesServices.cs
similarity index 62%
rename from src/Squidex/Config/Domain/WriteServices.cs
rename to src/Squidex/Config/Domain/EntitiesServices.cs
index d77e4588a..cabc20e5c 100644
--- a/src/Squidex/Config/Domain/WriteServices.cs
+++ b/src/Squidex/Config/Domain/EntitiesServices.cs
@@ -6,10 +6,12 @@
// ==========================================================================
using System;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Migrate_01;
using Migrate_01.Migrations;
+using Orleans;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Entities;
@@ -19,25 +21,55 @@ using Squidex.Domain.Apps.Entities.Apps.Templates;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Commands;
+using Squidex.Domain.Apps.Entities.Contents.Edm;
+using Squidex.Domain.Apps.Entities.Contents.GraphQL;
+using Squidex.Domain.Apps.Entities.History;
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.Assets;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Migrations;
+using Squidex.Pipeline;
using Squidex.Pipeline.CommandMiddlewares;
namespace Squidex.Config.Domain
{
- public static class WriteServices
+ public static class EntitiesServices
{
- public static void AddMyWriteServices(this IServiceCollection services)
+ public static void AddMyEntitiesServices(this IServiceCollection services, IConfiguration config)
{
- services.AddSingletonAs()
- .As();
+ var exposeSourceUrl = config.GetOptionalValue("assetStore:exposeSourceUrl", true);
- services.AddSingletonAs()
- .As();
+ services.AddSingletonAs(c => new GraphQLUrlGenerator(
+ c.GetRequiredService>(),
+ c.GetRequiredService(),
+ exposeSourceUrl))
+ .As();
+
+ services.AddSingletonAs()
+ .As();
+
+ services.AddSingletonAs()
+ .As();
+
+ services.AddSingletonAs()
+ .As();
+
+ services.AddSingletonAs()
+ .As();
+
+ services.AddSingletonAs()
+ .As();
+
+ services.AddSingletonAs()
+ .As();
+
+ services.AddSingletonAs()
+ .AsSelf();
+
+ services.AddSingletonAs()
+ .As();
services.AddSingletonAs()
.As();
@@ -54,19 +86,19 @@ namespace Squidex.Config.Domain
services.AddSingletonAs()
.As();
- services.AddSingletonAs>()
+ services.AddSingletonAs()
.As();
- services.AddSingletonAs()
+ services.AddSingletonAs>()
.As();
- services.AddSingletonAs>()
+ services.AddSingletonAs>()
.As();
- services.AddSingletonAs>()
+ services.AddSingletonAs>()
.As();
- services.AddSingletonAs>()
+ services.AddSingletonAs>()
.As();
services.AddSingletonAs()
@@ -75,26 +107,10 @@ namespace Squidex.Config.Domain
services.AddSingletonAs()
.As();
- services.AddTransientAs()
- .As();
-
- services.AddTransientAs()
- .As();
-
- services.AddTransientAs()
- .As();
-
- services.AddTransientAs()
- .As();
-
- services.AddTransientAs()
- .As();
-
- services.AddTransientAs()
- .As();
+ services.AddSingletonAs()
+ .As();
- services.AddTransientAs()
- .AsSelf();
+ services.AddSingleton>(DomainObjectGrainFormatter.Format);
services.AddTransientAs()
.AsSelf();
@@ -111,13 +127,13 @@ namespace Squidex.Config.Domain
services.AddTransientAs()
.AsSelf();
- services.AddSingleton(c =>
+ services.AddSingleton(c =>
{
- var config = c.GetRequiredService>();
+ var uiOptions = c.GetRequiredService>();
var result = new InitialPatterns();
- foreach (var pattern in config.Value.RegexSuggestions)
+ foreach (var pattern in uiOptions.Value.RegexSuggestions)
{
if (!string.IsNullOrWhiteSpace(pattern.Key) &&
!string.IsNullOrWhiteSpace(pattern.Value))
@@ -129,5 +145,32 @@ namespace Squidex.Config.Domain
return result;
});
}
+
+ public static void AddMyMigrationServices(this IServiceCollection services)
+ {
+ services.AddSingletonAs()
+ .AsSelf();
+
+ services.AddTransientAs()
+ .As();
+
+ services.AddTransientAs()
+ .As();
+
+ services.AddTransientAs()
+ .As();
+
+ services.AddTransientAs()
+ .As();
+
+ services.AddTransientAs()
+ .As();
+
+ services.AddTransientAs()
+ .As();
+
+ services.AddTransientAs()
+ .AsSelf();
+ }
}
}
diff --git a/src/Squidex/Config/Domain/EventStoreServices.cs b/src/Squidex/Config/Domain/EventStoreServices.cs
index 85f28033e..baa744514 100644
--- a/src/Squidex/Config/Domain/EventStoreServices.cs
+++ b/src/Squidex/Config/Domain/EventStoreServices.cs
@@ -5,12 +5,15 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
+using System.Linq;
using EventStore.ClientAPI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Driver;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
+using Squidex.Infrastructure.EventSourcing.Grains;
+using Squidex.Infrastructure.States;
namespace Squidex.Config.Domain
{
@@ -48,6 +51,23 @@ namespace Squidex.Config.Domain
.As();
}
});
+
+ services.AddSingletonAs()
+ .As()
+ .As();
+
+ services.AddSingletonAs()
+ .As();
+
+ services.AddSingletonAs()
+ .As();
+
+ services.AddSingletonAs(c =>
+ {
+ var allEventConsumers = c.GetServices();
+
+ return new EventConsumerFactory(n => allEventConsumers.FirstOrDefault(x => x.Name == n));
+ });
}
}
}
diff --git a/src/Squidex/Config/Domain/InfrastructureServices.cs b/src/Squidex/Config/Domain/InfrastructureServices.cs
index 108d9791b..085e84e61 100644
--- a/src/Squidex/Config/Domain/InfrastructureServices.cs
+++ b/src/Squidex/Config/Domain/InfrastructureServices.cs
@@ -5,66 +5,20 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
-using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Infrastructure;
-using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
-using Newtonsoft.Json;
using NodaTime;
-using Squidex.Infrastructure;
-using Squidex.Infrastructure.Assets;
-using Squidex.Infrastructure.Assets.ImageSharp;
-using Squidex.Infrastructure.Commands;
-using Squidex.Infrastructure.EventSourcing;
-using Squidex.Infrastructure.Log;
-using Squidex.Infrastructure.Migrations;
-using Squidex.Infrastructure.States;
using Squidex.Infrastructure.UsageTracking;
-using Squidex.Pipeline;
+
+#pragma warning disable RECS0092 // Convert field to readonly
namespace Squidex.Config.Domain
{
public static class InfrastructureServices
{
- public static void AddMyInfrastructureServices(this IServiceCollection services, IConfiguration config)
+ public static void AddMyInfrastructureServices(this IServiceCollection services)
{
- if (config.GetValue("logging:human"))
- {
- services.AddSingletonAs(c => new Func