diff --git a/source/ApplyDBMigrationsApp/ApplyDBMigrationsApp.csproj b/source/ApplyDBMigrationsApp/ApplyDBMigrationsApp.csproj index 5cd19f75f0..621269594e 100644 --- a/source/ApplyDBMigrationsApp/ApplyDBMigrationsApp.csproj +++ b/source/ApplyDBMigrationsApp/ApplyDBMigrationsApp.csproj @@ -25,7 +25,7 @@ limitations under the License. - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/ArchitectureTests/ArchitectureTests.csproj b/source/ArchitectureTests/ArchitectureTests.csproj index 5e0cedfb02..8b297e8862 100644 --- a/source/ArchitectureTests/ArchitectureTests.csproj +++ b/source/ArchitectureTests/ArchitectureTests.csproj @@ -22,14 +22,14 @@ limitations under the License. - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/ArchivedMessages.Application/ArchivedMessages.Application.csproj b/source/ArchivedMessages.Application/ArchivedMessages.Application.csproj index 235c70fde8..7cc7c8fa91 100644 --- a/source/ArchivedMessages.Application/ArchivedMessages.Application.csproj +++ b/source/ArchivedMessages.Application/ArchivedMessages.Application.csproj @@ -4,7 +4,7 @@ Energinet.DataHub.EDI.ArchivedMessages.Application - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/ArchivedMessages.Domain/ArchivedMessages.Domain.csproj b/source/ArchivedMessages.Domain/ArchivedMessages.Domain.csproj index e056c42ed9..42cb27ced7 100644 --- a/source/ArchivedMessages.Domain/ArchivedMessages.Domain.csproj +++ b/source/ArchivedMessages.Domain/ArchivedMessages.Domain.csproj @@ -4,7 +4,7 @@ Energinet.DataHub.EDI.ArchivedMessages.Domain - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/ArchivedMessages.Infrastructure/ArchivedMessages.Infrastructure.csproj b/source/ArchivedMessages.Infrastructure/ArchivedMessages.Infrastructure.csproj index d483cb6030..03e8272252 100644 --- a/source/ArchivedMessages.Infrastructure/ArchivedMessages.Infrastructure.csproj +++ b/source/ArchivedMessages.Infrastructure/ArchivedMessages.Infrastructure.csproj @@ -8,7 +8,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/ArchivedMessages.IntegrationTests/ArchivedMessages.IntegrationTests.csproj b/source/ArchivedMessages.IntegrationTests/ArchivedMessages.IntegrationTests.csproj index fc37da3b3e..07346d50cf 100644 --- a/source/ArchivedMessages.IntegrationTests/ArchivedMessages.IntegrationTests.csproj +++ b/source/ArchivedMessages.IntegrationTests/ArchivedMessages.IntegrationTests.csproj @@ -24,7 +24,7 @@ limitations under the License. - + diff --git a/source/ArchivedMessages.Interfaces/ArchivedMessages.Interfaces.csproj b/source/ArchivedMessages.Interfaces/ArchivedMessages.Interfaces.csproj index 1a5c7949be..cd9db23b79 100644 --- a/source/ArchivedMessages.Interfaces/ArchivedMessages.Interfaces.csproj +++ b/source/ArchivedMessages.Interfaces/ArchivedMessages.Interfaces.csproj @@ -4,7 +4,7 @@ Energinet.DataHub.EDI.ArchivedMessages.Interfaces - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/AuditLog/AuditLog.csproj b/source/AuditLog/AuditLog.csproj index eb57d2b93b..b9332595e3 100644 --- a/source/AuditLog/AuditLog.csproj +++ b/source/AuditLog/AuditLog.csproj @@ -8,7 +8,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/B2BApi.AppTests/B2BApi.AppTests.csproj b/source/B2BApi.AppTests/B2BApi.AppTests.csproj index 65311d82ce..b14930657c 100644 --- a/source/B2BApi.AppTests/B2BApi.AppTests.csproj +++ b/source/B2BApi.AppTests/B2BApi.AppTests.csproj @@ -14,7 +14,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + all @@ -22,7 +22,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/B2BApi/B2BApi.csproj b/source/B2BApi/B2BApi.csproj index 2d6f0eb24c..a39770dfe8 100644 --- a/source/B2BApi/B2BApi.csproj +++ b/source/B2BApi/B2BApi.csproj @@ -42,7 +42,7 @@ limitations under the License. - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/B2CWebApi.AppTests/B2CWebApi.AppTests.csproj b/source/B2CWebApi.AppTests/B2CWebApi.AppTests.csproj index bb23e5fd1d..ddf870af6b 100644 --- a/source/B2CWebApi.AppTests/B2CWebApi.AppTests.csproj +++ b/source/B2CWebApi.AppTests/B2CWebApi.AppTests.csproj @@ -20,7 +20,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/B2CWebApi/B2CWebApi.csproj b/source/B2CWebApi/B2CWebApi.csproj index 0cc450c567..1aff022e8f 100644 --- a/source/B2CWebApi/B2CWebApi.csproj +++ b/source/B2CWebApi/B2CWebApi.csproj @@ -11,10 +11,10 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/BuildingBlocks.Application/BuildingBlocks.Application.csproj b/source/BuildingBlocks.Application/BuildingBlocks.Application.csproj index 99f17c252e..eb788b27ca 100644 --- a/source/BuildingBlocks.Application/BuildingBlocks.Application.csproj +++ b/source/BuildingBlocks.Application/BuildingBlocks.Application.csproj @@ -11,16 +11,16 @@ - + - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/BuildingBlocks.Application/FeatureFlag/FeatureFlagName.cs b/source/BuildingBlocks.Application/FeatureFlag/FeatureFlagName.cs index c2280bf4ed..dba0a41b63 100644 --- a/source/BuildingBlocks.Application/FeatureFlag/FeatureFlagName.cs +++ b/source/BuildingBlocks.Application/FeatureFlag/FeatureFlagName.cs @@ -39,4 +39,9 @@ public enum FeatureFlagName /// Whether to use orchestration for handling RequestWholesaleServices processes. /// UseRequestWholesaleServicesProcessOrchestration, + + /// + /// Whether to use the new incoming message parser. + /// + UseNewIncomingMessageParser, } diff --git a/source/BuildingBlocks.Application/FeatureFlag/IFeatureFlagManager.cs b/source/BuildingBlocks.Application/FeatureFlag/IFeatureFlagManager.cs index 996552ba72..53cfb689ea 100644 --- a/source/BuildingBlocks.Application/FeatureFlag/IFeatureFlagManager.cs +++ b/source/BuildingBlocks.Application/FeatureFlag/IFeatureFlagManager.cs @@ -43,4 +43,9 @@ public interface IFeatureFlagManager /// Whether to use the RequestWholesaleServices process orchestration. /// Task UseRequestWholesaleServicesProcessOrchestrationAsync(); + + /// + /// Whether to use the new incoming message parser. + /// + Task UseNewIncomingMessageParserAsync(); } diff --git a/source/BuildingBlocks.Application/FeatureFlag/MicrosoftFeatureFlagManager.cs b/source/BuildingBlocks.Application/FeatureFlag/MicrosoftFeatureFlagManager.cs index 96fe0ba4f6..4dcd3ad912 100644 --- a/source/BuildingBlocks.Application/FeatureFlag/MicrosoftFeatureFlagManager.cs +++ b/source/BuildingBlocks.Application/FeatureFlag/MicrosoftFeatureFlagManager.cs @@ -37,5 +37,7 @@ public MicrosoftFeatureFlagManager(IFeatureManager featureManager) public Task UseRequestWholesaleServicesProcessOrchestrationAsync() => IsEnabledAsync(FeatureFlagName.UseRequestWholesaleServicesProcessOrchestration); + public Task UseNewIncomingMessageParserAsync() => IsEnabledAsync(FeatureFlagName.UseNewIncomingMessageParser); + private Task IsEnabledAsync(FeatureFlagName featureFlagName) => _featureManager.IsEnabledAsync(featureFlagName.ToString()); } diff --git a/source/BuildingBlocks.Domain/BuildingBlocks.Domain.csproj b/source/BuildingBlocks.Domain/BuildingBlocks.Domain.csproj index 12ff23a8d6..666ee793bf 100644 --- a/source/BuildingBlocks.Domain/BuildingBlocks.Domain.csproj +++ b/source/BuildingBlocks.Domain/BuildingBlocks.Domain.csproj @@ -4,7 +4,7 @@ Energinet.DataHub.EDI.BuildingBlocks.Domain - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/BuildingBlocks.Infrastructure/BuildingBlocks.Infrastructure.csproj b/source/BuildingBlocks.Infrastructure/BuildingBlocks.Infrastructure.csproj index 372330b0fa..9c6a1ad166 100644 --- a/source/BuildingBlocks.Infrastructure/BuildingBlocks.Infrastructure.csproj +++ b/source/BuildingBlocks.Infrastructure/BuildingBlocks.Infrastructure.csproj @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/BuildingBlocks.Interfaces/BuildingBlocks.Interfaces.csproj b/source/BuildingBlocks.Interfaces/BuildingBlocks.Interfaces.csproj index 2d2f9d1c24..88930e467d 100644 --- a/source/BuildingBlocks.Interfaces/BuildingBlocks.Interfaces.csproj +++ b/source/BuildingBlocks.Interfaces/BuildingBlocks.Interfaces.csproj @@ -5,7 +5,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/BuildingBlocks.Tests/BuildingBlocks.Tests.csproj b/source/BuildingBlocks.Tests/BuildingBlocks.Tests.csproj index 90eb93ef26..70fa4faf16 100644 --- a/source/BuildingBlocks.Tests/BuildingBlocks.Tests.csproj +++ b/source/BuildingBlocks.Tests/BuildingBlocks.Tests.csproj @@ -25,8 +25,8 @@ limitations under the License. - - + + diff --git a/source/BuildingBlocks.Tests/TestDoubles/FeatureFlagManagerStub.cs b/source/BuildingBlocks.Tests/TestDoubles/FeatureFlagManagerStub.cs index e1069c098a..e63b1b7cb6 100644 --- a/source/BuildingBlocks.Tests/TestDoubles/FeatureFlagManagerStub.cs +++ b/source/BuildingBlocks.Tests/TestDoubles/FeatureFlagManagerStub.cs @@ -30,4 +30,6 @@ public class FeatureFlagManagerStub : IFeatureFlagManager public Task ReceiveMeteredDataForMeasurementPointsAsync() => Task.FromResult(true); public Task UseRequestWholesaleServicesProcessOrchestrationAsync() => Task.FromResult(false); + + public Task UseNewIncomingMessageParserAsync() => Task.FromResult(true); } diff --git a/source/CalculationResults/CalculationResults.Infrastructure/CalculationResults.Infrastructure.csproj b/source/CalculationResults/CalculationResults.Infrastructure/CalculationResults.Infrastructure.csproj index 58f7486e98..0855de7b82 100644 --- a/source/CalculationResults/CalculationResults.Infrastructure/CalculationResults.Infrastructure.csproj +++ b/source/CalculationResults/CalculationResults.Infrastructure/CalculationResults.Infrastructure.csproj @@ -7,7 +7,7 @@ - + @@ -17,7 +17,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/CalculationResults/CalculationResults.Interfaces/CalculationResults.Interfaces.csproj b/source/CalculationResults/CalculationResults.Interfaces/CalculationResults.Interfaces.csproj index fb772402f6..6500ac5a19 100644 --- a/source/CalculationResults/CalculationResults.Interfaces/CalculationResults.Interfaces.csproj +++ b/source/CalculationResults/CalculationResults.Interfaces/CalculationResults.Interfaces.csproj @@ -5,7 +5,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/Contracts/Contracts.csproj b/source/Contracts/Contracts.csproj index 81b1ef5ca5..2e783491bc 100644 --- a/source/Contracts/Contracts.csproj +++ b/source/Contracts/Contracts.csproj @@ -29,7 +29,7 @@ limitations under the License. all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/DataAccess.UnitOfWork/DataAccess.UnitOfWork.csproj b/source/DataAccess.UnitOfWork/DataAccess.UnitOfWork.csproj index d8a563211a..2f095b2a80 100644 --- a/source/DataAccess.UnitOfWork/DataAccess.UnitOfWork.csproj +++ b/source/DataAccess.UnitOfWork/DataAccess.UnitOfWork.csproj @@ -4,7 +4,7 @@ Energinet.DataHub.EDI.DataAccess.UnitOfWork - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/DataAccess/DataAccess.csproj b/source/DataAccess/DataAccess.csproj index a800026547..3619f0e594 100644 --- a/source/DataAccess/DataAccess.csproj +++ b/source/DataAccess/DataAccess.csproj @@ -10,9 +10,9 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/Edi.UnitTests/Edi.UnitTests.csproj b/source/Edi.UnitTests/Edi.UnitTests.csproj index 5d84e9e907..1249203571 100644 --- a/source/Edi.UnitTests/Edi.UnitTests.csproj +++ b/source/Edi.UnitTests/Edi.UnitTests.csproj @@ -6,9 +6,9 @@ true - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -26,7 +26,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/Edi/Edi.csproj b/source/Edi/Edi.csproj index 51b915e800..38dc347044 100644 --- a/source/Edi/Edi.csproj +++ b/source/Edi/Edi.csproj @@ -6,12 +6,12 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/IncomingMessages.Application/Extensions/DependencyInjection/IncomingMessagesExtensions.cs b/source/IncomingMessages.Application/Extensions/DependencyInjection/IncomingMessagesExtensions.cs index dcb1bcb0a4..7cb0ab755d 100644 --- a/source/IncomingMessages.Application/Extensions/DependencyInjection/IncomingMessagesExtensions.cs +++ b/source/IncomingMessages.Application/Extensions/DependencyInjection/IncomingMessagesExtensions.cs @@ -19,6 +19,7 @@ using Energinet.DataHub.Core.Messaging.Communication.Extensions.Builder; using Energinet.DataHub.Core.Messaging.Communication.Extensions.DependencyInjection; using Energinet.DataHub.Core.Messaging.Communication.Extensions.Options; +using Energinet.DataHub.EDI.BuildingBlocks.Domain.Models; using Energinet.DataHub.EDI.DataAccess.Extensions.DependencyInjection; using Energinet.DataHub.EDI.IncomingMessages.Application.UseCases; using Energinet.DataHub.EDI.IncomingMessages.Domain.Validation; @@ -27,6 +28,7 @@ using Energinet.DataHub.EDI.IncomingMessages.Infrastructure.Configuration.Options; using Energinet.DataHub.EDI.IncomingMessages.Infrastructure.MessageParsers; using Energinet.DataHub.EDI.IncomingMessages.Infrastructure.MessageParsers.AggregatedMeasureDataRequestMessageParsers; +using Energinet.DataHub.EDI.IncomingMessages.Infrastructure.MessageParsers.MeteredDateForMeasurementPointParsers; using Energinet.DataHub.EDI.IncomingMessages.Infrastructure.MessageParsers.MeteredDateForMeasurementPointParsers.Ebix; using Energinet.DataHub.EDI.IncomingMessages.Infrastructure.MessageParsers.WholesaleSettlementMessageParsers; using Energinet.DataHub.EDI.IncomingMessages.Infrastructure.Repositories.MessageId; @@ -133,6 +135,15 @@ public static IServiceCollection AddIncomingMessagesModule(this IServiceCollecti .AddSingleton() .AddSingleton() .AddSingleton(); + + services + .AddTransient(); + + services.AddTransient>(provider => new Dictionary<(IncomingDocumentType, DocumentFormat), IMessageParser> + { + { (IncomingDocumentType.MeteredDataForMeasurementPoint, DocumentFormat.Ebix), provider.GetRequiredService() }, + }); + return services; } } diff --git a/source/IncomingMessages.Application/IncomingMessages.Application.csproj b/source/IncomingMessages.Application/IncomingMessages.Application.csproj index e33509c6f0..92eceb8c34 100644 --- a/source/IncomingMessages.Application/IncomingMessages.Application.csproj +++ b/source/IncomingMessages.Application/IncomingMessages.Application.csproj @@ -15,7 +15,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/IncomingMessages.Application/UseCases/ReceiveIncomingMarketMessage.cs b/source/IncomingMessages.Application/UseCases/ReceiveIncomingMarketMessage.cs index b6b3853f93..b35f3b6932 100644 --- a/source/IncomingMessages.Application/UseCases/ReceiveIncomingMarketMessage.cs +++ b/source/IncomingMessages.Application/UseCases/ReceiveIncomingMarketMessage.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using BuildingBlocks.Application.FeatureFlag; using Energinet.DataHub.EDI.ArchivedMessages.Interfaces; using Energinet.DataHub.EDI.ArchivedMessages.Interfaces.Models; using Energinet.DataHub.EDI.BuildingBlocks.Domain.Authentication; @@ -31,6 +32,8 @@ namespace Energinet.DataHub.EDI.IncomingMessages.Application.UseCases; public class ReceiveIncomingMarketMessage { private readonly MarketMessageParser _marketMessageParser; + private readonly IDictionary<(IncomingDocumentType, DocumentFormat), IMessageParser> _messageParsers; + private readonly IFeatureFlagManager _featureFlagManager; private readonly ValidateIncomingMessage _validateIncomingMessage; private readonly ResponseFactory _responseFactory; private readonly IArchivedMessagesClient _archivedMessagesClient; @@ -42,6 +45,8 @@ public class ReceiveIncomingMarketMessage public ReceiveIncomingMarketMessage( MarketMessageParser marketMessageParser, + IDictionary<(IncomingDocumentType DocumentType, DocumentFormat DocumentFormat), IMessageParser> messageParsers, + IFeatureFlagManager featureFlagManager, ValidateIncomingMessage validateIncomingMessage, ResponseFactory responseFactory, IArchivedMessagesClient archivedMessagesClient, @@ -52,6 +57,8 @@ public ReceiveIncomingMarketMessage( AuthenticatedActor actorAuthenticator) { _marketMessageParser = marketMessageParser; + _messageParsers = messageParsers; + _featureFlagManager = featureFlagManager; _validateIncomingMessage = validateIncomingMessage; _responseFactory = responseFactory; _archivedMessagesClient = archivedMessagesClient; @@ -71,11 +78,12 @@ public async Task ReceiveIncomingMarketMessageAsync( { ArgumentNullException.ThrowIfNull(documentType); ArgumentNullException.ThrowIfNull(incomingMarketMessageStream); - - var incomingMarketMessageParserResult = - await _marketMessageParser.ParseAsync(incomingMarketMessageStream, incomingDocumentFormat, documentType, cancellationToken) - .ConfigureAwait(false); - + var incomingMarketMessageParserResult = await ParseIncomingMessageAsync( + incomingMarketMessageStream, + incomingDocumentFormat, + documentType, + cancellationToken) + .ConfigureAwait(false); if (incomingMarketMessageParserResult.Errors.Count != 0 || incomingMarketMessageParserResult.IncomingMessage == null) { @@ -139,6 +147,24 @@ private static bool ShouldArchive(IncomingDocumentType documentType) return documentType != IncomingDocumentType.MeteredDataForMeasurementPoint; } + private async Task ParseIncomingMessageAsync( + IIncomingMarketMessageStream incomingMarketMessageStream, + DocumentFormat documentFormat, + IncomingDocumentType documentType, + CancellationToken cancellationToken) + { + if (await _featureFlagManager.UseNewIncomingMessageParserAsync().ConfigureAwait(false)) + { + if (_messageParsers.TryGetValue((documentType, documentFormat), out var messageParser)) + { + return await messageParser.ParseAsync(incomingMarketMessageStream, cancellationToken).ConfigureAwait(false); + } + } + + return await _marketMessageParser.ParseAsync(incomingMarketMessageStream, documentFormat, documentType, cancellationToken) + .ConfigureAwait(false); + } + private async Task ArchiveIncomingMessageAsync( IIncomingMarketMessageStream incomingMarketMessageStream, IIncomingMessage incomingMessage, diff --git a/source/IncomingMessages.Domain/IncomingMessages.Domain.csproj b/source/IncomingMessages.Domain/IncomingMessages.Domain.csproj index 5723948fcf..184f7e8bff 100644 --- a/source/IncomingMessages.Domain/IncomingMessages.Domain.csproj +++ b/source/IncomingMessages.Domain/IncomingMessages.Domain.csproj @@ -10,7 +10,7 @@ enable - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/IncomingMessages.Infrastructure/IncomingMessages.Infrastructure.csproj b/source/IncomingMessages.Infrastructure/IncomingMessages.Infrastructure.csproj index cd36ca747d..25eb422742 100644 --- a/source/IncomingMessages.Infrastructure/IncomingMessages.Infrastructure.csproj +++ b/source/IncomingMessages.Infrastructure/IncomingMessages.Infrastructure.csproj @@ -20,9 +20,9 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/IncomingMessages.Infrastructure/MessageParsers/BaseParsers/Ebix/MessageHeaderExtractor.cs b/source/IncomingMessages.Infrastructure/MessageParsers/BaseParsers/Ebix/MessageHeaderExtractor.cs deleted file mode 100644 index a833f42aab..0000000000 --- a/source/IncomingMessages.Infrastructure/MessageParsers/BaseParsers/Ebix/MessageHeaderExtractor.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2020 Energinet DataHub A/S -// -// Licensed under the Apache License, Version 2.0 (the "License2"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Xml.Linq; -using Energinet.DataHub.EDI.BuildingBlocks.Domain.Models; - -namespace Energinet.DataHub.EDI.IncomingMessages.Infrastructure.MessageParsers.BaseParsers.Ebix; - -internal static class MessageHeaderExtractor -{ - private const string HeaderElementName = "HeaderEnergyDocument"; - private const string EnergyContextElementName = "ProcessEnergyContext"; - private const string Identification = "Identification"; - private const string DocumentType = "DocumentType"; - private const string Creation = "Creation"; - private const string SenderEnergyParty = "SenderEnergyParty"; - private const string RecipientEnergyParty = "RecipientEnergyParty"; - private const string EnergyBusinessProcess = "EnergyBusinessProcess"; - private const string EnergyBusinessProcessRole = "EnergyBusinessProcessRole"; - private const string EnergyIndustryClassification = "EnergyIndustryClassification"; - - public static MessageHeader Extract( - XDocument document, - XNamespace ns) - { - var headerElement = document.Descendants(ns + HeaderElementName).SingleOrDefault(); - if (headerElement == null) throw new InvalidOperationException("Header element not found"); - - var messageId = headerElement.Element(ns + Identification)?.Value ?? string.Empty; - var messageType = headerElement.Element(ns + DocumentType)?.Value ?? string.Empty; - var createdAt = headerElement.Element(ns + Creation)?.Value ?? string.Empty; - var senderId = headerElement.Element(ns + SenderEnergyParty)?.Element(ns + Identification)?.Value ?? string.Empty; - var receiverId = headerElement.Element(ns + RecipientEnergyParty)?.Element(ns + Identification)?.Value ?? string.Empty; - - var energyContextElement = document.Descendants(ns + EnergyContextElementName).FirstOrDefault(); - if (energyContextElement == null) throw new InvalidOperationException("Energy Context element not found"); - - var businessReason = energyContextElement.Element(ns + EnergyBusinessProcess)?.Value ?? string.Empty; - var senderRole = energyContextElement.Element(ns + EnergyBusinessProcessRole)?.Value ?? string.Empty; - var businessType = energyContextElement.Element(ns + EnergyIndustryClassification)?.Value; - - return new MessageHeader( - messageId, - messageType, - businessReason, - senderId, - senderRole, - receiverId, - // ReceiverRole is not specified in incoming Ebix documents - ActorRole.MeteredDataAdministrator.Code, - createdAt, - businessType); - } -} diff --git a/source/IncomingMessages.Infrastructure/MessageParsers/EbixMessageParserBase.cs b/source/IncomingMessages.Infrastructure/MessageParsers/EbixMessageParserBase.cs new file mode 100644 index 0000000000..d7a2509ddb --- /dev/null +++ b/source/IncomingMessages.Infrastructure/MessageParsers/EbixMessageParserBase.cs @@ -0,0 +1,215 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Xml; +using System.Xml.Linq; +using System.Xml.Schema; +using Energinet.DataHub.EDI.BuildingBlocks.Domain.Models; +using Energinet.DataHub.EDI.IncomingMessages.Domain.Abstractions; +using Energinet.DataHub.EDI.IncomingMessages.Domain.Validation.ValidationErrors; +using Energinet.DataHub.EDI.IncomingMessages.Infrastructure.MessageParsers.BaseParsers; +using Energinet.DataHub.EDI.IncomingMessages.Infrastructure.Schemas.Ebix; + +namespace Energinet.DataHub.EDI.IncomingMessages.Infrastructure.MessageParsers; + +public abstract class EbixMessageParserBase(EbixSchemaProvider schemaProvider) : MessageParserBase() +{ + private const string HeaderElementName = "HeaderEnergyDocument"; + private const string EnergyContextElementName = "ProcessEnergyContext"; + private const string Identification = "Identification"; + private const string DocumentType = "DocumentType"; + private const string Creation = "Creation"; + private const string SenderEnergyParty = "SenderEnergyParty"; + private const string RecipientEnergyParty = "RecipientEnergyParty"; + private const string EnergyBusinessProcess = "EnergyBusinessProcess"; + private const string EnergyBusinessProcessRole = "EnergyBusinessProcessRole"; + private const string EnergyIndustryClassification = "EnergyIndustryClassification"; + private readonly EbixSchemaProvider _schemaProvider = schemaProvider; + + protected abstract string RootPayloadElementName { get; } + + protected override async Task ParseMessageAsync( + IIncomingMarketMessageStream marketMessage, + XmlSchema schemaResult, + CancellationToken cancellationToken) + { + using var reader = XmlReader.Create(marketMessage.Stream, CreateXmlReaderSettings(schemaResult)); + if (Errors.Count > 0) + { + return new IncomingMarketMessageParserResult(Errors.ToArray()); + } + + var document = await XDocument.LoadAsync(reader, LoadOptions.None, cancellationToken).ConfigureAwait(false); + var @namespace = GetNamespace(marketMessage); + var ns = XNamespace.Get(@namespace); + + var header = ParseHeader(document, ns); + var transactions = ParseTransactions(document, ns, header.SenderId); + + if (Errors.Count != 0) + { + return new IncomingMarketMessageParserResult(Errors.ToArray()); + } + + return CreateResult(header, transactions); + } + + protected override async Task<(XmlSchema? Schema, IncomingMarketMessageParserResult? Result)> GetSchemaAsync(IIncomingMarketMessageStream marketMessage, CancellationToken cancellationToken) + { + string? @namespace = null; + IncomingMarketMessageParserResult? parserResult = null; + XmlSchema? xmlSchema = default; + try + { + @namespace = GetNamespace(marketMessage); + var version = GetVersion(@namespace); + var businessProcessType = BusinessProcessType(@namespace); + xmlSchema = await _schemaProvider.GetSchemaAsync(businessProcessType, version, cancellationToken) + .ConfigureAwait(true); + + if (xmlSchema is null) + { + parserResult = new IncomingMarketMessageParserResult( + new InvalidBusinessReasonOrVersion(businessProcessType, version)); + } + } + catch (XmlException exception) + { + parserResult = Invalid(exception); + } + catch (ObjectDisposedException objectDisposedException) + { + parserResult = Invalid(objectDisposedException); + } + catch (IndexOutOfRangeException indexOutOfRangeException) + { + parserResult = Invalid(indexOutOfRangeException); + } + + return (xmlSchema, parserResult); + } + + protected abstract IReadOnlyCollection ParseTransactions(XDocument document, XNamespace ns, string senderNumber); + + protected abstract IncomingMarketMessageParserResult CreateResult(MessageHeader header, IReadOnlyCollection transactions); + + private static string[] SplitNamespace(string @namespace) + { + ArgumentNullException.ThrowIfNull(@namespace); + return @namespace.Split(':'); + } + + private string GetNamespace(IIncomingMarketMessageStream marketMessage) + { + ArgumentNullException.ThrowIfNull(marketMessage); + + var settings = new XmlReaderSettings + { + Async = true, + IgnoreWhitespace = true, + IgnoreComments = true, + }; + + using var reader = XmlReader.Create(marketMessage.Stream, settings); + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element && reader.Name.Contains(RootPayloadElementName)) + { + return reader.NamespaceURI; + } + } + + throw new XmlException($"Namespace for element '{RootPayloadElementName}' not found."); + } + + private string BusinessProcessType(string @namespace) + { + ArgumentNullException.ThrowIfNull(@namespace); + var split = SplitNamespace(@namespace); + if (split.Length < 5) + { + throw new XmlException($"Invalid namespace format"); + } + + var businessReason = split[4]; + var parts = businessReason.Split('-'); + return parts.Last(); + } + + private string GetVersion(string @namespace) + { + ArgumentNullException.ThrowIfNull(@namespace); + var split = SplitNamespace(@namespace); + if (split.Length < 6) + { + throw new XmlException($"Invalid namespace format"); + } + + var version = split[5]; + return version.StartsWith('v') ? version[1..] : version; + } + + private MessageHeader ParseHeader(XDocument document, XNamespace ns) + { + var headerElement = document.Descendants(ns + HeaderElementName).SingleOrDefault(); + if (headerElement == null) throw new InvalidOperationException("Header element not found"); + + var messageId = headerElement.Element(ns + Identification)?.Value ?? string.Empty; + var messageType = headerElement.Element(ns + DocumentType)?.Value ?? string.Empty; + var createdAt = headerElement.Element(ns + Creation)?.Value ?? string.Empty; + var senderId = headerElement.Element(ns + SenderEnergyParty)?.Element(ns + Identification)?.Value ?? string.Empty; + var receiverId = headerElement.Element(ns + RecipientEnergyParty)?.Element(ns + Identification)?.Value ?? string.Empty; + + var energyContextElement = document.Descendants(ns + EnergyContextElementName).FirstOrDefault(); + if (energyContextElement == null) throw new InvalidOperationException("Energy Context element not found"); + + var businessReason = energyContextElement.Element(ns + EnergyBusinessProcess)?.Value ?? string.Empty; + var senderRole = energyContextElement.Element(ns + EnergyBusinessProcessRole)?.Value ?? string.Empty; + var businessType = energyContextElement.Element(ns + EnergyIndustryClassification)?.Value; + + return new MessageHeader( + messageId, + messageType, + businessReason, + senderId, + senderRole, + receiverId, + // ReceiverRole is not specified in incoming Ebix documents + ActorRole.MeteredDataAdministrator.Code, + createdAt, + businessType); + } + + private XmlReaderSettings CreateXmlReaderSettings(XmlSchema xmlSchema) + { + var settings = new XmlReaderSettings + { + Async = true, + ValidationType = ValidationType.Schema, + ValidationFlags = XmlSchemaValidationFlags.ProcessInlineSchema | + XmlSchemaValidationFlags.ReportValidationWarnings, + }; + + settings.Schemas.Add(xmlSchema); + settings.ValidationEventHandler += OnValidationError; + return settings; + } + + private void OnValidationError(object? sender, ValidationEventArgs arguments) + { + var message = + $"XML schema validation error at line {arguments.Exception.LineNumber}, position {arguments.Exception.LinePosition}: {arguments.Message}."; + Errors.Add(InvalidMessageStructure.From(message)); + } +} diff --git a/source/ProcessManager.Core/Infrastructure/Database/UnitOfWork.cs b/source/IncomingMessages.Infrastructure/MessageParsers/IMessageParser.cs similarity index 58% rename from source/ProcessManager.Core/Infrastructure/Database/UnitOfWork.cs rename to source/IncomingMessages.Infrastructure/MessageParsers/IMessageParser.cs index 6cf2941491..8ae2eb732e 100644 --- a/source/ProcessManager.Core/Infrastructure/Database/UnitOfWork.cs +++ b/source/IncomingMessages.Infrastructure/MessageParsers/IMessageParser.cs @@ -12,21 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Energinet.DataHub.ProcessManagement.Core.Application; +using Energinet.DataHub.EDI.BuildingBlocks.Domain.Models; -namespace Energinet.DataHub.ProcessManagement.Core.Infrastructure.Database; +namespace Energinet.DataHub.EDI.IncomingMessages.Infrastructure.MessageParsers; -public class UnitOfWork : IUnitOfWork +public interface IMessageParser { - private readonly ProcessManagerContext _context; - - public UnitOfWork(ProcessManagerContext context) - { - _context = context; - } - - public async Task CommitAsync() - { - await _context.SaveChangesAsync().ConfigureAwait(false); - } + Task ParseAsync( + IIncomingMarketMessageStream marketMessage, + CancellationToken cancellationToken); } diff --git a/source/IncomingMessages.Infrastructure/MessageParsers/MarketMessageParser.cs b/source/IncomingMessages.Infrastructure/MessageParsers/MarketMessageParser.cs index 623d1df1e0..cf3d14a0dc 100644 --- a/source/IncomingMessages.Infrastructure/MessageParsers/MarketMessageParser.cs +++ b/source/IncomingMessages.Infrastructure/MessageParsers/MarketMessageParser.cs @@ -34,7 +34,7 @@ public Task ParseAsync( var parser = _parsers.FirstOrDefault(parser => parser.HandledFormat.Equals(documentFormat) && parser.DocumentType.Equals(documentType)); if (parser is null) - throw new InvalidOperationException($"No message parser found for message format '{documentFormat}' and document type '{documentType}'"); + throw new NotSupportedException($"No message parser found for message format '{documentFormat}' and document type '{documentType}'"); return parser.ParseAsync(marketMessage, cancellationToken); } } diff --git a/source/IncomingMessages.Infrastructure/MessageParsers/MessageParserBase.cs b/source/IncomingMessages.Infrastructure/MessageParsers/MessageParserBase.cs new file mode 100644 index 0000000000..2eb594349d --- /dev/null +++ b/source/IncomingMessages.Infrastructure/MessageParsers/MessageParserBase.cs @@ -0,0 +1,55 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Collections.ObjectModel; +using System.Xml; +using Energinet.DataHub.EDI.BuildingBlocks.Domain.Models; +using Energinet.DataHub.EDI.IncomingMessages.Domain.Validation.ValidationErrors; +using Energinet.DataHub.EDI.IncomingMessages.Infrastructure.Schemas; + +namespace Energinet.DataHub.EDI.IncomingMessages.Infrastructure.MessageParsers; + +public abstract class MessageParserBase() : IMessageParser +{ + protected Collection Errors { get; } = []; + + public async Task ParseAsync( + IIncomingMarketMessageStream marketMessage, + CancellationToken cancellationToken) + { + var schemaResult = await GetSchemaAsync(marketMessage, cancellationToken).ConfigureAwait(false); + if (schemaResult.Schema == null) + { + return schemaResult.Result ?? new IncomingMarketMessageParserResult(new InvalidSchemaOrNamespace()); + } + + return await ParseMessageAsync(marketMessage, schemaResult.Schema, cancellationToken) + .ConfigureAwait(false); + } + + protected static IncomingMarketMessageParserResult Invalid( + Exception exception) + { + return new IncomingMarketMessageParserResult( + InvalidMessageStructure.From(exception)); + } + + protected abstract Task<(TSchema? Schema, IncomingMarketMessageParserResult? Result)> + GetSchemaAsync(IIncomingMarketMessageStream marketMessage, CancellationToken cancellationToken); + + protected abstract Task ParseMessageAsync( + IIncomingMarketMessageStream marketMessage, + TSchema schemaResult, + CancellationToken cancellationToken); +} diff --git a/source/IncomingMessages.Infrastructure/MessageParsers/MeteredDateForMeasurementPointParsers/Ebix/MeteredDataForMeasurementPointEbixMessageParser.cs b/source/IncomingMessages.Infrastructure/MessageParsers/MeteredDateForMeasurementPointParsers/Ebix/MeteredDataForMeasurementPointEbixMessageParser.cs index 638d7606dd..6eea4b4dcf 100644 --- a/source/IncomingMessages.Infrastructure/MessageParsers/MeteredDateForMeasurementPointParsers/Ebix/MeteredDataForMeasurementPointEbixMessageParser.cs +++ b/source/IncomingMessages.Infrastructure/MessageParsers/MeteredDateForMeasurementPointParsers/Ebix/MeteredDataForMeasurementPointEbixMessageParser.cs @@ -12,220 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.Collections.ObjectModel; -using System.Xml; -using System.Xml.Linq; -using System.Xml.Schema; using Energinet.DataHub.EDI.BuildingBlocks.Domain.Models; -using Energinet.DataHub.EDI.IncomingMessages.Domain; -using Energinet.DataHub.EDI.IncomingMessages.Domain.Validation.ValidationErrors; -using Energinet.DataHub.EDI.IncomingMessages.Infrastructure.Schemas.Ebix; -using Microsoft.Extensions.Logging; -using MessageHeaderExtractor = Energinet.DataHub.EDI.IncomingMessages.Infrastructure.MessageParsers.BaseParsers.Ebix.MessageHeaderExtractor; namespace Energinet.DataHub.EDI.IncomingMessages.Infrastructure.MessageParsers.MeteredDateForMeasurementPointParsers.Ebix; -public class MeteredDataForMeasurementPointEbixMessageParser(EbixSchemaProvider schemaProvider, ILogger logger) : IMarketMessageParser +public class MeteredDataForMeasurementPointEbixMessageParser(EbixMessageParser messageParser) : IMarketMessageParser { - private const string RootPayloadElementName = "DK_MeteredDataTimeSeries"; - private readonly EbixSchemaProvider _schemaProvider = schemaProvider; - private readonly ILogger _logger = logger; + private readonly EbixMessageParser _messageParser = messageParser; public DocumentFormat HandledFormat => DocumentFormat.Ebix; public IncomingDocumentType DocumentType => IncomingDocumentType.MeteredDataForMeasurementPoint; - private Collection Errors { get; } = []; - public async Task ParseAsync( IIncomingMarketMessageStream incomingMarketMessageStream, CancellationToken cancellationToken) { - var xmlSchemaResult = await GetSchemaAsync(incomingMarketMessageStream, cancellationToken).ConfigureAwait(false); - if (xmlSchemaResult.Schema == null || xmlSchemaResult.Namespace == null) - { - return xmlSchemaResult.ParserResult ?? new IncomingMarketMessageParserResult(new InvalidSchemaOrNamespace()); - } - - using var reader = XmlReader.Create(incomingMarketMessageStream.Stream, CreateXmlReaderSettings(xmlSchemaResult.Schema)); - if (Errors.Count > 0) - { - return new IncomingMarketMessageParserResult(Errors.ToArray()); - } - - try - { - var parsedXmlData = await ParseXmlDataAsync(reader, xmlSchemaResult.Namespace, cancellationToken).ConfigureAwait(false); - - if (Errors.Count != 0) - { - _logger.LogError("Errors found after parsing XML data: {Errors}", Errors); - return new IncomingMarketMessageParserResult(Errors.ToArray()); - } - - return parsedXmlData; - } - catch (XmlException exception) - { - _logger.LogError(exception, "Ebix parsing error during data extraction"); - return InvalidEbixFailure(exception); - } - catch (ObjectDisposedException objectDisposedException) - { - _logger.LogError(objectDisposedException, "Stream was disposed during data extraction"); - return InvalidEbixFailure(objectDisposedException); - } - } - - private static IncomingMarketMessageParserResult InvalidEbixFailure( - Exception exception) - { - return new IncomingMarketMessageParserResult( - InvalidMessageStructure.From(exception)); - } - - private static string BusinessProcessType(string @namespace) - { - ArgumentNullException.ThrowIfNull(@namespace); - var split = SplitNamespace(@namespace); - if (split.Length < 5) - { - throw new XmlException($"Invalid namespace format"); - } - - var businessReason = split[4]; - var parts = businessReason.Split('-'); - return parts.Last(); - } - - private static string GetVersion(string @namespace) - { - ArgumentNullException.ThrowIfNull(@namespace); - var split = SplitNamespace(@namespace); - if (split.Length < 6) - { - throw new XmlException($"Invalid namespace format"); - } - - var version = split[5]; - return version.StartsWith('v') ? version[1..] : version; - } - - private static string[] SplitNamespace(string @namespace) - { - ArgumentNullException.ThrowIfNull(@namespace); - return @namespace.Split(':'); - } - - private static string GetNamespace(IIncomingMarketMessageStream marketMessage) - { - ArgumentNullException.ThrowIfNull(marketMessage); - - var settings = new XmlReaderSettings - { - Async = true, - IgnoreWhitespace = true, - IgnoreComments = true, - }; - - using var reader = XmlReader.Create(marketMessage.Stream, settings); - while (reader.Read()) - { - if (reader.NodeType == XmlNodeType.Element && reader.Name.Contains(RootPayloadElementName)) - { - return reader.NamespaceURI; - } - } - - throw new XmlException($"Namespace for element '{RootPayloadElementName}' not found."); - } - - private async Task ParseXmlDataAsync( - XmlReader reader, - string @namespace, - CancellationToken cancellationToken) - { - var document = await XDocument.LoadAsync(reader, LoadOptions.None, cancellationToken).ConfigureAwait(false); - var ns = XNamespace.Get(@namespace); - - var header = MessageHeaderExtractor.Extract(document, ns); - var listOfSeries = MeteredDataForMeasurementPointSeriesExtractor - .ParseSeries(document, ns, header.SenderId) - .ToList(); - - return new IncomingMarketMessageParserResult(new MeteredDataForMeasurementPointMessage( - header.MessageId, - header.MessageType, - header.CreatedAt, - header.SenderId, - header.ReceiverId, - header.SenderRole, - header.BusinessReason, - header.ReceiverRole, - header.BusinessType, - listOfSeries.AsReadOnly())); - } - - private async Task<(XmlSchema? Schema, string? Namespace, IncomingMarketMessageParserResult? ParserResult)> GetSchemaAsync( - IIncomingMarketMessageStream incomingMarketMessageStream, - CancellationToken cancellationToken) - { - string? @namespace = null; - IncomingMarketMessageParserResult? parserResult = null; - XmlSchema? xmlSchema = null; - try - { - @namespace = GetNamespace(incomingMarketMessageStream); - var version = GetVersion(@namespace); - var businessProcessType = BusinessProcessType(@namespace); - xmlSchema = await _schemaProvider.GetSchemaAsync(businessProcessType, version, cancellationToken) - .ConfigureAwait(true); - - if (xmlSchema is null) - { - _logger.LogError("Schema not found for business process type {BusinessProcessType} and version {Version}", businessProcessType, version); - parserResult = new IncomingMarketMessageParserResult( - new InvalidBusinessReasonOrVersion(businessProcessType, version)); - } - } - catch (XmlException exception) - { - _logger.LogWarning(exception, "Ebix parsing error"); - parserResult = InvalidEbixFailure(exception); - } - catch (ObjectDisposedException objectDisposedException) - { - _logger.LogWarning(objectDisposedException, "Stream was disposed"); - parserResult = InvalidEbixFailure(objectDisposedException); - } - catch (IndexOutOfRangeException indexOutOfRangeException) - { - _logger.LogWarning(indexOutOfRangeException, "Namespace format is invalid"); - parserResult = InvalidEbixFailure(indexOutOfRangeException); - } - - return (xmlSchema, @namespace, parserResult); - } - - private XmlReaderSettings CreateXmlReaderSettings(XmlSchema xmlSchema) - { - var settings = new XmlReaderSettings - { - Async = true, - ValidationType = ValidationType.Schema, - ValidationFlags = XmlSchemaValidationFlags.ProcessInlineSchema | - XmlSchemaValidationFlags.ReportValidationWarnings, - }; - - settings.Schemas.Add(xmlSchema); - settings.ValidationEventHandler += OnValidationError; - return settings; - } - - private void OnValidationError(object? sender, ValidationEventArgs arguments) - { - var message = - $"XML schema validation error at line {arguments.Exception.LineNumber}, position {arguments.Exception.LinePosition}: {arguments.Message}."; - Errors.Add(InvalidMessageStructure.From(message)); + return await _messageParser.ParseAsync(incomingMarketMessageStream, cancellationToken).ConfigureAwait(false); } } diff --git a/source/IncomingMessages.Infrastructure/MessageParsers/MeteredDateForMeasurementPointParsers/Ebix/MeteredDataForMeasurementPointSeriesExtractor.cs b/source/IncomingMessages.Infrastructure/MessageParsers/MeteredDateForMeasurementPointParsers/Ebix/MeteredDataForMeasurementPointSeriesExtractor.cs deleted file mode 100644 index 5e1356b0e1..0000000000 --- a/source/IncomingMessages.Infrastructure/MessageParsers/MeteredDateForMeasurementPointParsers/Ebix/MeteredDataForMeasurementPointSeriesExtractor.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2020 Energinet DataHub A/S -// -// Licensed under the Apache License, Version 2.0 (the "License2"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Xml.Linq; -using Energinet.DataHub.EDI.IncomingMessages.Domain; - -namespace Energinet.DataHub.EDI.IncomingMessages.Infrastructure.MessageParsers.MeteredDateForMeasurementPointParsers.Ebix; - -public static class MeteredDataForMeasurementPointSeriesExtractor -{ - private const string SeriesElementName = "PayloadEnergyTimeSeries"; - private const string Identification = "Identification"; - private const string ResolutionDuration = "ResolutionDuration"; - private const string ObservationTimeSeriesPeriod = "ObservationTimeSeriesPeriod"; - private const string Start = "Start"; - private const string End = "End"; - private const string IncludedProductCharacteristic = "IncludedProductCharacteristic"; - private const string UnitType = "UnitType"; - private const string DetailMeasurementMeteringPointCharacteristic = "DetailMeasurementMeteringPointCharacteristic"; - private const string TypeOfMeteringPoint = "TypeOfMeteringPoint"; - private const string MeteringPointDomainLocation = "MeteringPointDomainLocation"; - private const string Position = "Position"; - private const string EnergyQuantity = "EnergyQuantity"; - private const string QuantityQuality = "QuantityQuality"; - private const string IntervalEnergyObservation = "IntervalEnergyObservation"; - - internal static IEnumerable ParseSeries( - XDocument document, - XNamespace ns, - string senderNumber) - { - var seriesElements = document.Descendants(ns + SeriesElementName); - - foreach (var seriesElement in seriesElements) - { - var id = seriesElement.Element(ns + Identification)?.Value ?? string.Empty; - var resolution = seriesElement.Element(ns + ObservationTimeSeriesPeriod)?.Element(ns + ResolutionDuration)?.Value; - var startDateAndOrTimeDateTime = seriesElement.Element(ns + ObservationTimeSeriesPeriod)?.Element(ns + Start)?.Value ?? string.Empty; - var endDateAndOrTimeDateTime = seriesElement.Element(ns + ObservationTimeSeriesPeriod)?.Element(ns + End)?.Value; - var productNumber = seriesElement.Element(ns + IncludedProductCharacteristic)?.Element(ns + Identification)?.Value; - var productUnitType = seriesElement.Element(ns + IncludedProductCharacteristic)?.Element(ns + UnitType)?.Value; - var meteringPointType = seriesElement.Element(ns + DetailMeasurementMeteringPointCharacteristic)?.Element(ns + TypeOfMeteringPoint)?.Value; - var meteringPointLocationId = seriesElement.Element(ns + MeteringPointDomainLocation)?.Element(ns + Identification)?.Value; - - var energyObservations = seriesElement - .Descendants(ns + IntervalEnergyObservation) - .Select(e => new EnergyObservation( - e.Element(ns + Position)?.Value, - e.Element(ns + EnergyQuantity)?.Value, - e.Element(ns + QuantityQuality)?.Value)) - .ToList(); - - yield return new MeteredDataForMeasurementPointSeries( - id, - resolution, - startDateAndOrTimeDateTime, - endDateAndOrTimeDateTime, - productNumber, - productUnitType, - meteringPointType, - meteringPointLocationId, - senderNumber, - energyObservations); - } - } -} diff --git a/source/IncomingMessages.Infrastructure/MessageParsers/MeteredDateForMeasurementPointParsers/EbixMessageParser.cs b/source/IncomingMessages.Infrastructure/MessageParsers/MeteredDateForMeasurementPointParsers/EbixMessageParser.cs new file mode 100644 index 0000000000..c49bf01843 --- /dev/null +++ b/source/IncomingMessages.Infrastructure/MessageParsers/MeteredDateForMeasurementPointParsers/EbixMessageParser.cs @@ -0,0 +1,96 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Xml.Linq; +using Energinet.DataHub.EDI.IncomingMessages.Domain; +using Energinet.DataHub.EDI.IncomingMessages.Domain.Abstractions; +using Energinet.DataHub.EDI.IncomingMessages.Infrastructure.MessageParsers.BaseParsers; +using Energinet.DataHub.EDI.IncomingMessages.Infrastructure.Schemas.Ebix; + +namespace Energinet.DataHub.EDI.IncomingMessages.Infrastructure.MessageParsers.MeteredDateForMeasurementPointParsers; + +public class EbixMessageParser(EbixSchemaProvider schemaProvider) : EbixMessageParserBase(schemaProvider) +{ + private const string SeriesElementName = "PayloadEnergyTimeSeries"; + private const string Identification = "Identification"; + private const string ResolutionDuration = "ResolutionDuration"; + private const string ObservationTimeSeriesPeriod = "ObservationTimeSeriesPeriod"; + private const string Start = "Start"; + private const string End = "End"; + private const string IncludedProductCharacteristic = "IncludedProductCharacteristic"; + private const string UnitType = "UnitType"; + private const string DetailMeasurementMeteringPointCharacteristic = "DetailMeasurementMeteringPointCharacteristic"; + private const string MeteringPointType = "TypeOfMeteringPoint"; + private const string MeteringPointDomainLocation = "MeteringPointDomainLocation"; + private const string Position = "Position"; + private const string EnergyQuantity = "EnergyQuantity"; + private const string QuantityQuality = "QuantityQuality"; + private const string IntervalEnergyObservation = "IntervalEnergyObservation"; + + protected override string RootPayloadElementName => "DK_MeteredDataTimeSeries"; + + protected override IReadOnlyCollection ParseTransactions(XDocument document, XNamespace ns, string senderNumber) + { + var transactionElements = document.Descendants(ns + SeriesElementName); + var result = new List(); + foreach (var transactionElement in transactionElements) + { + var id = transactionElement.Element(ns + Identification)?.Value ?? string.Empty; + var resolution = transactionElement.Element(ns + ObservationTimeSeriesPeriod)?.Element(ns + ResolutionDuration)?.Value; + var startDateAndOrTimeDateTime = transactionElement.Element(ns + ObservationTimeSeriesPeriod)?.Element(ns + Start)?.Value ?? string.Empty; + var endDateAndOrTimeDateTime = transactionElement.Element(ns + ObservationTimeSeriesPeriod)?.Element(ns + End)?.Value; + var productNumber = transactionElement.Element(ns + IncludedProductCharacteristic)?.Element(ns + Identification)?.Value; + var productUnitType = transactionElement.Element(ns + IncludedProductCharacteristic)?.Element(ns + UnitType)?.Value; + var meteringPointType = transactionElement.Element(ns + DetailMeasurementMeteringPointCharacteristic)?.Element(ns + MeteringPointType)?.Value; + var meteringPointLocationId = transactionElement.Element(ns + MeteringPointDomainLocation)?.Element(ns + Identification)?.Value; + + var energyObservations = transactionElement + .Descendants(ns + IntervalEnergyObservation) + .Select(e => new EnergyObservation( + e.Element(ns + Position)?.Value, + e.Element(ns + EnergyQuantity)?.Value, + e.Element(ns + QuantityQuality)?.Value)) + .ToList(); + + result.Add(new MeteredDataForMeasurementPointSeries( + id, + resolution, + startDateAndOrTimeDateTime, + endDateAndOrTimeDateTime, + productNumber, + productUnitType, + meteringPointType, + meteringPointLocationId, + senderNumber, + energyObservations)); + } + + return result.AsReadOnly(); + } + + protected override IncomingMarketMessageParserResult CreateResult(MessageHeader header, IReadOnlyCollection transactions) + { + return new IncomingMarketMessageParserResult(new MeteredDataForMeasurementPointMessage( + header.MessageId, + header.MessageType, + header.CreatedAt, + header.SenderId, + header.ReceiverId, + header.SenderRole, + header.BusinessReason, + header.ReceiverRole, + header.BusinessType, + transactions)); + } +} diff --git a/source/IncomingMessages.IntegrationTests/IncomingMessages.IntegrationTests.csproj b/source/IncomingMessages.IntegrationTests/IncomingMessages.IntegrationTests.csproj index 649564f1bf..c115a18ab2 100644 --- a/source/IncomingMessages.IntegrationTests/IncomingMessages.IntegrationTests.csproj +++ b/source/IncomingMessages.IntegrationTests/IncomingMessages.IntegrationTests.csproj @@ -28,7 +28,7 @@ limitations under the License. - + diff --git a/source/IncomingMessages.IntegrationTests/MessageParsers/GivenNewDocumentTypeTests.cs b/source/IncomingMessages.IntegrationTests/MessageParsers/GivenNewDocumentTypeTests.cs index 4eade98878..48f250d1fe 100644 --- a/source/IncomingMessages.IntegrationTests/MessageParsers/GivenNewDocumentTypeTests.cs +++ b/source/IncomingMessages.IntegrationTests/MessageParsers/GivenNewDocumentTypeTests.cs @@ -84,11 +84,11 @@ public async Task When_ParsingMessageOfDocumentTypeAndFormat_Then_ExpectedMessag // Assert if (_unsupportedCombinationsOfIncomingDocumentTypeAndDocumentFormat.Contains((incomingDocumentType, documentFormat))) { - await act.Should().ThrowAsync("because this combination is not supported"); + await act.Should().ThrowAsync("because this combination is not supported"); } else { - await act.Should().NotThrowAsync("because this combination is valid, but no parser was found"); + await act.Should().NotThrowAsync("because this combination is valid, but no parser was found"); } } } diff --git a/source/IncomingMessages.Interfaces/IncomingMessages.Interfaces.csproj b/source/IncomingMessages.Interfaces/IncomingMessages.Interfaces.csproj index 792ee9d670..88f8d27a6b 100644 --- a/source/IncomingMessages.Interfaces/IncomingMessages.Interfaces.csproj +++ b/source/IncomingMessages.Interfaces/IncomingMessages.Interfaces.csproj @@ -6,7 +6,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/IntegrationEvents.Application/IntegrationEvents.Application.csproj b/source/IntegrationEvents.Application/IntegrationEvents.Application.csproj index 9ff8aebb37..cd92422459 100644 --- a/source/IntegrationEvents.Application/IntegrationEvents.Application.csproj +++ b/source/IntegrationEvents.Application/IntegrationEvents.Application.csproj @@ -18,7 +18,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/IntegrationEvents.Infrastructure/IntegrationEvents.Infrastructure.csproj b/source/IntegrationEvents.Infrastructure/IntegrationEvents.Infrastructure.csproj index d5e0dac893..e02ee80bc2 100644 --- a/source/IntegrationEvents.Infrastructure/IntegrationEvents.Infrastructure.csproj +++ b/source/IntegrationEvents.Infrastructure/IntegrationEvents.Infrastructure.csproj @@ -13,12 +13,12 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/IntegrationEvents.IntegrationTests/IntegrationEvents.IntegrationTests.csproj b/source/IntegrationEvents.IntegrationTests/IntegrationEvents.IntegrationTests.csproj index d808312b52..5ef75177ab 100644 --- a/source/IntegrationEvents.IntegrationTests/IntegrationEvents.IntegrationTests.csproj +++ b/source/IntegrationEvents.IntegrationTests/IntegrationEvents.IntegrationTests.csproj @@ -9,7 +9,7 @@ - + @@ -21,7 +21,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/source/IntegrationTests/IntegrationTests.csproj b/source/IntegrationTests/IntegrationTests.csproj index 57ad4c7d94..30ab46c50c 100644 --- a/source/IntegrationTests/IntegrationTests.csproj +++ b/source/IntegrationTests/IntegrationTests.csproj @@ -24,7 +24,7 @@ limitations under the License. - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -32,7 +32,7 @@ limitations under the License. - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/MasterData.Application/MasterData.Application.csproj b/source/MasterData.Application/MasterData.Application.csproj index da2ffd7f2c..4894870f99 100644 --- a/source/MasterData.Application/MasterData.Application.csproj +++ b/source/MasterData.Application/MasterData.Application.csproj @@ -5,7 +5,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/MasterData.Domain/MasterData.Domain.csproj b/source/MasterData.Domain/MasterData.Domain.csproj index dd6ad4878e..16c894fed8 100644 --- a/source/MasterData.Domain/MasterData.Domain.csproj +++ b/source/MasterData.Domain/MasterData.Domain.csproj @@ -4,7 +4,7 @@ Energinet.DataHub.EDI.MasterData.Domain - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/MasterData.Infrastructure/MasterData.Infrastructure.csproj b/source/MasterData.Infrastructure/MasterData.Infrastructure.csproj index c46b87689b..223b216d88 100644 --- a/source/MasterData.Infrastructure/MasterData.Infrastructure.csproj +++ b/source/MasterData.Infrastructure/MasterData.Infrastructure.csproj @@ -15,7 +15,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/MasterData.IntegrationTests/MasterData.IntegrationTests.csproj b/source/MasterData.IntegrationTests/MasterData.IntegrationTests.csproj index 170d420741..8a9a7c1369 100644 --- a/source/MasterData.IntegrationTests/MasterData.IntegrationTests.csproj +++ b/source/MasterData.IntegrationTests/MasterData.IntegrationTests.csproj @@ -9,7 +9,7 @@ - + diff --git a/source/MasterData.Interfaces/MasterData.Interfaces.csproj b/source/MasterData.Interfaces/MasterData.Interfaces.csproj index b9076e2257..748901237a 100644 --- a/source/MasterData.Interfaces/MasterData.Interfaces.csproj +++ b/source/MasterData.Interfaces/MasterData.Interfaces.csproj @@ -4,7 +4,7 @@ Energinet.DataHub.EDI.MasterData.Interfaces - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/Outbox.Infrastructure/Outbox.Infrastructure.csproj b/source/Outbox.Infrastructure/Outbox.Infrastructure.csproj index f1bb380831..b80660de46 100644 --- a/source/Outbox.Infrastructure/Outbox.Infrastructure.csproj +++ b/source/Outbox.Infrastructure/Outbox.Infrastructure.csproj @@ -7,7 +7,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/OutgoingMessages.Application/OutgoingMessages.Application.csproj b/source/OutgoingMessages.Application/OutgoingMessages.Application.csproj index 460383e390..d7ec12b623 100644 --- a/source/OutgoingMessages.Application/OutgoingMessages.Application.csproj +++ b/source/OutgoingMessages.Application/OutgoingMessages.Application.csproj @@ -8,7 +8,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/OutgoingMessages.Domain/OutgoingMessages.Domain.csproj b/source/OutgoingMessages.Domain/OutgoingMessages.Domain.csproj index 8ded45efd3..21f6b7c576 100644 --- a/source/OutgoingMessages.Domain/OutgoingMessages.Domain.csproj +++ b/source/OutgoingMessages.Domain/OutgoingMessages.Domain.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/OutgoingMessages.Infrastructure/OutgoingMessages.Infrastructure.csproj b/source/OutgoingMessages.Infrastructure/OutgoingMessages.Infrastructure.csproj index bcd33d3b5f..47aa234f2f 100644 --- a/source/OutgoingMessages.Infrastructure/OutgoingMessages.Infrastructure.csproj +++ b/source/OutgoingMessages.Infrastructure/OutgoingMessages.Infrastructure.csproj @@ -10,7 +10,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/OutgoingMessages.IntegrationTests/OutgoingMessages.IntegrationTests.csproj b/source/OutgoingMessages.IntegrationTests/OutgoingMessages.IntegrationTests.csproj index be6dd187ea..4aeb0f5cb6 100644 --- a/source/OutgoingMessages.IntegrationTests/OutgoingMessages.IntegrationTests.csproj +++ b/source/OutgoingMessages.IntegrationTests/OutgoingMessages.IntegrationTests.csproj @@ -13,7 +13,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/source/OutgoingMessages.Interfaces/OutgoingMessages.Interfaces.csproj b/source/OutgoingMessages.Interfaces/OutgoingMessages.Interfaces.csproj index f96e2370e1..1d5d687e11 100644 --- a/source/OutgoingMessages.Interfaces/OutgoingMessages.Interfaces.csproj +++ b/source/OutgoingMessages.Interfaces/OutgoingMessages.Interfaces.csproj @@ -6,7 +6,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/Process.Application/Process.Application.csproj b/source/Process.Application/Process.Application.csproj index 48d54dd076..23b9644ef7 100644 --- a/source/Process.Application/Process.Application.csproj +++ b/source/Process.Application/Process.Application.csproj @@ -10,7 +10,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/Process.Domain/Process.Domain.csproj b/source/Process.Domain/Process.Domain.csproj index b2e37ff5f4..fa51d88c3b 100644 --- a/source/Process.Domain/Process.Domain.csproj +++ b/source/Process.Domain/Process.Domain.csproj @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/Process.Infrastructure/Process.Infrastructure.csproj b/source/Process.Infrastructure/Process.Infrastructure.csproj index d67b0d6cdd..4328f016b6 100644 --- a/source/Process.Infrastructure/Process.Infrastructure.csproj +++ b/source/Process.Infrastructure/Process.Infrastructure.csproj @@ -8,16 +8,16 @@ - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/source/Process.Interfaces/Process.Interfaces.csproj b/source/Process.Interfaces/Process.Interfaces.csproj index 3278ac04b3..af6a5536f4 100644 --- a/source/Process.Interfaces/Process.Interfaces.csproj +++ b/source/Process.Interfaces/Process.Interfaces.csproj @@ -6,7 +6,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/ProcessManager.Core.Tests/Integration/Infrastructure/Orchestration/OrchestrationInstanceRepositoryTests.cs b/source/ProcessManager.Core.Tests/Integration/Infrastructure/Orchestration/OrchestrationInstanceRepositoryTests.cs index 1af38a6e38..5e6c215460 100644 --- a/source/ProcessManager.Core.Tests/Integration/Infrastructure/Orchestration/OrchestrationInstanceRepositoryTests.cs +++ b/source/ProcessManager.Core.Tests/Integration/Infrastructure/Orchestration/OrchestrationInstanceRepositoryTests.cs @@ -32,14 +32,12 @@ public class OrchestrationInstanceRepositoryTests : IAsyncLifetime private readonly ProcessManagerCoreFixture _fixture; private readonly ProcessManagerContext _dbContext; private readonly OrchestrationInstanceRepository _sut; - private readonly UnitOfWork _unitOfWork; public OrchestrationInstanceRepositoryTests(ProcessManagerCoreFixture fixture) { _fixture = fixture; _dbContext = _fixture.DatabaseManager.CreateDbContext(); _sut = new OrchestrationInstanceRepository(_dbContext); - _unitOfWork = new UnitOfWork(_dbContext); } public Task InitializeAsync() @@ -98,7 +96,7 @@ public async Task GivenOrchestrationDescriptionNotInDatabase_WhenAddOrchestratio // Act await _sut.AddAsync(newOrchestrationInstance); - var act = _unitOfWork.CommitAsync; + var act = () => _sut.UnitOfWork.CommitAsync(); // Assert await act.Should() @@ -118,7 +116,7 @@ public async Task GivenOrchestrationDescriptionInDatabase_WhenAddOrchestrationIn // Act await _sut.AddAsync(newOrchestrationInstance); - await _unitOfWork.CommitAsync(); + await _sut.UnitOfWork.CommitAsync(); // Assert var actual = await _sut.GetAsync(newOrchestrationInstance.Id); @@ -146,7 +144,7 @@ public async Task GivenScheduledOrchestrationInstancesInDatabase_WhenGetSchedule runAt: SystemClock.Instance.GetCurrentInstant().PlusDays(5)); await _sut.AddAsync(scheduledIntoTheFarFuture); - await _unitOfWork.CommitAsync(); + await _sut.UnitOfWork.CommitAsync(); // Act var actual = await _sut.FindAsync( @@ -177,7 +175,7 @@ public async Task GivenOrchestrationInstancesInDatabase_WhenSearchByName_ThenExp var basedOn02 = CreateOrchestrationInstance(existingOrchestrationDescription02); await _sut.AddAsync(basedOn02); - await _unitOfWork.CommitAsync(); + await _sut.UnitOfWork.CommitAsync(); // Act var actual = await _sut.SearchAsync(existingOrchestrationDescription01.Name); @@ -204,7 +202,7 @@ public async Task GivenOrchestrationInstancesInDatabase_WhenSearchByNameAndVersi var basedOnV2 = CreateOrchestrationInstance(existingOrchestrationDescriptionV2); await _sut.AddAsync(basedOnV2); - await _unitOfWork.CommitAsync(); + await _sut.UnitOfWork.CommitAsync(); // Act var actual = await _sut.SearchAsync(existingOrchestrationDescriptionV1.Name, existingOrchestrationDescriptionV1.Version); @@ -241,7 +239,7 @@ public async Task GivenOrchestrationInstancesInDatabase_WhenSearchByNameAndLifec isRunningV2.Lifecycle.TransitionToRunning(SystemClock.Instance); await _sut.AddAsync(isRunningV2); - await _unitOfWork.CommitAsync(); + await _sut.UnitOfWork.CommitAsync(); // Act var actual = await _sut.SearchAsync(existingOrchestrationDescriptionV1.Name, lifecycleState: OrchestrationInstanceLifecycleStates.Running); @@ -280,7 +278,7 @@ public async Task GivenOrchestrationInstancesInDatabase_WhenSearchByNameAndTermi isTerminatedAsFailedV2.Lifecycle.TransitionToTerminated(SystemClock.Instance, OrchestrationInstanceTerminationStates.Failed); await _sut.AddAsync(isTerminatedAsFailedV2); - await _unitOfWork.CommitAsync(); + await _sut.UnitOfWork.CommitAsync(); // Act var actual = await _sut.SearchAsync( @@ -319,7 +317,7 @@ public async Task GivenOrchestrationInstancesInDatabase_WhenSearchByNameAndStart isRunning02.Lifecycle.TransitionToRunning(SystemClock.Instance); await _sut.AddAsync(isRunning02); - await _unitOfWork.CommitAsync(); + await _sut.UnitOfWork.CommitAsync(); // Act var actual = await _sut.SearchAsync( @@ -364,7 +362,7 @@ public async Task GivenOrchestrationInstancesInDatabase_WhenSearchByNameAndTermi isTerminated02.Lifecycle.TransitionToTerminated(SystemClock.Instance, OrchestrationInstanceTerminationStates.Succeeded); await _sut.AddAsync(isTerminated02); - await _unitOfWork.CommitAsync(); + await _sut.UnitOfWork.CommitAsync(); // Act var actual = await _sut.SearchAsync( diff --git a/source/ProcessManager.Core.Tests/ProcessManager.Core.Tests.csproj b/source/ProcessManager.Core.Tests/ProcessManager.Core.Tests.csproj index 7bb7170cc9..7eb59953a1 100644 --- a/source/ProcessManager.Core.Tests/ProcessManager.Core.Tests.csproj +++ b/source/ProcessManager.Core.Tests/ProcessManager.Core.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/source/ProcessManager.Core/Application/Orchestration/ICancelScheduledOrchestrationInstanceCommand.cs b/source/ProcessManager.Core/Application/Orchestration/ICancelScheduledOrchestrationInstanceCommand.cs new file mode 100644 index 0000000000..43a958e9eb --- /dev/null +++ b/source/ProcessManager.Core/Application/Orchestration/ICancelScheduledOrchestrationInstanceCommand.cs @@ -0,0 +1,25 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; + +namespace Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; + +public interface ICancelScheduledOrchestrationInstanceCommand +{ + /// + /// Cancel a scheduled orchestration instance. + /// + Task CancelScheduledOrchestrationInstanceAsync(OrchestrationInstanceId id); +} diff --git a/source/ProcessManager.Core/Application/Orchestration/IOrchestrationInstanceExecutor.cs b/source/ProcessManager.Core/Application/Orchestration/IOrchestrationInstanceExecutor.cs new file mode 100644 index 0000000000..f47712f925 --- /dev/null +++ b/source/ProcessManager.Core/Application/Orchestration/IOrchestrationInstanceExecutor.cs @@ -0,0 +1,29 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationDescription; +using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; + +namespace Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; + +/// +/// Abstracts the execution of orchestration instances from technology specific implementations. +/// +internal interface IOrchestrationInstanceExecutor +{ + /// + /// Start a new orchestration instance. + /// + Task StartNewOrchestrationInstanceAsync(OrchestrationDescription orchestrationDescription, OrchestrationInstance orchestrationInstance); +} diff --git a/source/ProcessManager.Core/Application/IOrchestrationInstanceProgressRepository.cs b/source/ProcessManager.Core/Application/Orchestration/IOrchestrationInstanceProgressRepository.cs similarity index 77% rename from source/ProcessManager.Core/Application/IOrchestrationInstanceProgressRepository.cs rename to source/ProcessManager.Core/Application/Orchestration/IOrchestrationInstanceProgressRepository.cs index 82bbb5a32b..f843f2f4ec 100644 --- a/source/ProcessManager.Core/Application/IOrchestrationInstanceProgressRepository.cs +++ b/source/ProcessManager.Core/Application/Orchestration/IOrchestrationInstanceProgressRepository.cs @@ -14,17 +14,22 @@ using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; -namespace Energinet.DataHub.ProcessManagement.Core.Application; +namespace Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; /// /// Use this from Durable Functions activities to get the orchestration instance and then -/// update its progress, before commiting changes back by using . +/// update its progress, before commiting changes back by using . /// public interface IOrchestrationInstanceProgressRepository { + /// + /// Use to save changes. + /// + public IUnitOfWork UnitOfWork { get; } + /// /// Get existing orchestration instance. - /// To commit changes use . + /// To commit changes use . /// Task GetAsync(OrchestrationInstanceId id); } diff --git a/source/ProcessManager.Core/Application/IOrchestrationInstanceRepository.cs b/source/ProcessManager.Core/Application/Orchestration/IOrchestrationInstanceQueries.cs similarity index 79% rename from source/ProcessManager.Core/Application/IOrchestrationInstanceRepository.cs rename to source/ProcessManager.Core/Application/Orchestration/IOrchestrationInstanceQueries.cs index 53c24150c8..f795ea1869 100644 --- a/source/ProcessManager.Core/Application/IOrchestrationInstanceRepository.cs +++ b/source/ProcessManager.Core/Application/Orchestration/IOrchestrationInstanceQueries.cs @@ -15,15 +15,14 @@ using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; using NodaTime; -namespace Energinet.DataHub.ProcessManagement.Core.Application; +namespace Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; -public interface IOrchestrationInstanceRepository : IOrchestrationInstanceProgressRepository +public interface IOrchestrationInstanceQueries { /// - /// Add the orchestration instance. - /// To commit changes use . + /// Get existing orchestration instance. /// - Task AddAsync(OrchestrationInstance orchestrationInstance); + Task GetAsync(OrchestrationInstanceId id); /// /// Get all orchestration instances filtered by their related orchestration definition name and version, diff --git a/source/ProcessManager.Core/Application/Orchestration/IOrchestrationInstanceRepository.cs b/source/ProcessManager.Core/Application/Orchestration/IOrchestrationInstanceRepository.cs new file mode 100644 index 0000000000..b9358e1b53 --- /dev/null +++ b/source/ProcessManager.Core/Application/Orchestration/IOrchestrationInstanceRepository.cs @@ -0,0 +1,50 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; +using NodaTime; + +namespace Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; + +internal interface IOrchestrationInstanceRepository +{ + /// + /// Use to save changes. + /// + public IUnitOfWork UnitOfWork { get; } + + /// + /// Add the orchestration instance. + /// To commit changes use . + /// + Task AddAsync(OrchestrationInstance orchestrationInstance); + + /// + /// Get existing orchestration instance. + /// To commit changes use . + /// + Task GetAsync(OrchestrationInstanceId id); + + /// + /// Get all orchestration instances filtered by their related orchestration definition name and version, + /// and their lifecycle / termination states. + /// + Task> SearchAsync( + string name, + int? version, + OrchestrationInstanceLifecycleStates? lifecycleState, + OrchestrationInstanceTerminationStates? terminationState, + Instant? startedAtOrLater, + Instant? terminatedAtOrEarlier); +} diff --git a/source/ProcessManager.Core/Application/IOrchestrationRegisterQueries.cs b/source/ProcessManager.Core/Application/Orchestration/IOrchestrationRegisterQueries.cs similarity index 88% rename from source/ProcessManager.Core/Application/IOrchestrationRegisterQueries.cs rename to source/ProcessManager.Core/Application/Orchestration/IOrchestrationRegisterQueries.cs index ad277f65a4..bad6cf7c80 100644 --- a/source/ProcessManager.Core/Application/IOrchestrationRegisterQueries.cs +++ b/source/ProcessManager.Core/Application/Orchestration/IOrchestrationRegisterQueries.cs @@ -14,12 +14,12 @@ using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationDescription; -namespace Energinet.DataHub.ProcessManagement.Core.Application; +namespace Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; /// /// Readonly access to the orchestration register. /// -public interface IOrchestrationRegisterQueries +internal interface IOrchestrationRegisterQueries { Task GetAsync(OrchestrationDescriptionId id); diff --git a/source/ProcessManager.Core/Application/IOrchestrationInstanceManager.cs b/source/ProcessManager.Core/Application/Orchestration/IStartOrchestrationInstanceCommands.cs similarity index 83% rename from source/ProcessManager.Core/Application/IOrchestrationInstanceManager.cs rename to source/ProcessManager.Core/Application/Orchestration/IStartOrchestrationInstanceCommands.cs index 5ed179cd92..e3d842ea86 100644 --- a/source/ProcessManager.Core/Application/IOrchestrationInstanceManager.cs +++ b/source/ProcessManager.Core/Application/Orchestration/IStartOrchestrationInstanceCommands.cs @@ -15,9 +15,9 @@ using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; using NodaTime; -namespace Energinet.DataHub.ProcessManagement.Core.Application; +namespace Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; -public interface IOrchestrationInstanceManager +public interface IStartOrchestrationInstanceCommands { /// /// Start a new instance of an orchestration. @@ -39,9 +39,4 @@ Task ScheduleNewOrchestrationInstanceAsync( Instant runAt, IReadOnlyCollection skipStepsBySequence) where TParameter : class; - - /// - /// Cancel a scheduled orchestration instance. - /// - Task CancelScheduledOrchestrationInstanceAsync(OrchestrationInstanceId id); } diff --git a/source/ProcessManager.Core/Application/IUnitOfWork.cs b/source/ProcessManager.Core/Application/Orchestration/IUnitOfWork.cs similarity index 81% rename from source/ProcessManager.Core/Application/IUnitOfWork.cs rename to source/ProcessManager.Core/Application/Orchestration/IUnitOfWork.cs index 1a51ba1ad7..9c644a7356 100644 --- a/source/ProcessManager.Core/Application/IUnitOfWork.cs +++ b/source/ProcessManager.Core/Application/Orchestration/IUnitOfWork.cs @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace Energinet.DataHub.ProcessManagement.Core.Application; +namespace Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; public interface IUnitOfWork { - Task CommitAsync(); + Task CommitAsync(CancellationToken cancellationToken = default); } diff --git a/source/ProcessManager.Core/Infrastructure/Orchestration/OrchestrationInstanceManager.cs b/source/ProcessManager.Core/Application/Orchestration/OrchestrationInstanceManager.cs similarity index 71% rename from source/ProcessManager.Core/Infrastructure/Orchestration/OrchestrationInstanceManager.cs rename to source/ProcessManager.Core/Application/Orchestration/OrchestrationInstanceManager.cs index cbf994fcd8..ea974f2a52 100644 --- a/source/ProcessManager.Core/Infrastructure/Orchestration/OrchestrationInstanceManager.cs +++ b/source/ProcessManager.Core/Application/Orchestration/OrchestrationInstanceManager.cs @@ -12,48 +12,30 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Energinet.DataHub.ProcessManagement.Core.Application; +using Energinet.DataHub.ProcessManagement.Core.Application.Scheduling; using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationDescription; using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; -using Microsoft.Azure.WebJobs.Extensions.DurableTask; using NodaTime; -namespace Energinet.DataHub.ProcessManagement.Core.Infrastructure.Orchestration; +namespace Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; /// -/// An encapsulation of that allows us to -/// provide a "framework" for managing Durable Functions orchestration instances using custom domain types. +/// An manager that allows us to provide a framework for managing orchestration instances +/// using custom domain types. /// -public class OrchestrationInstanceManager : IOrchestrationInstanceManager, IOrchestrationInstanceScheduleManager +internal class OrchestrationInstanceManager( + IClock clock, + IOrchestrationInstanceExecutor executor, + IOrchestrationRegisterQueries orchestrationRegister, + IOrchestrationInstanceRepository repository) : + IStartOrchestrationInstanceCommands, + IStartScheduledOrchestrationInstanceCommand, + ICancelScheduledOrchestrationInstanceCommand { - private readonly IClock _clock; - private readonly IDurableClient _durableClient; - private readonly IOrchestrationRegisterQueries _orchestrationRegister; - private readonly IOrchestrationInstanceRepository _orchestrationInstanceRepository; - private readonly IUnitOfWork _unitOfWork; - - /// - /// Construct manager. - /// - /// - /// Must be a Durable Task Client that is connected to - /// the same Task Hub as the Durable Functions host containing orchestrations. - /// - /// - /// - public OrchestrationInstanceManager( - IClock clock, - IDurableClient durableClient, - IOrchestrationRegisterQueries orchestrationRegister, - IOrchestrationInstanceRepository orchestrationInstanceRepository, - IUnitOfWork unitOfWork) - { - _clock = clock; - _durableClient = durableClient; - _orchestrationRegister = orchestrationRegister; - _orchestrationInstanceRepository = orchestrationInstanceRepository; - _unitOfWork = unitOfWork; - } + private readonly IClock _clock = clock; + private readonly IOrchestrationInstanceExecutor _executor = executor; + private readonly IOrchestrationRegisterQueries _orchestrationRegister = orchestrationRegister; + private readonly IOrchestrationInstanceRepository _repository = repository; /// public async Task StartNewOrchestrationInstanceAsync( @@ -92,7 +74,7 @@ public async Task ScheduleNewOrchestrationInstanceAsync /// public async Task StartScheduledOrchestrationInstanceAsync(OrchestrationInstanceId id) { - var orchestrationInstance = await _orchestrationInstanceRepository.GetAsync(id).ConfigureAwait(false); + var orchestrationInstance = await _repository.GetAsync(id).ConfigureAwait(false); if (!orchestrationInstance.Lifecycle.IsPendingForScheduledStart()) throw new InvalidOperationException("Orchestration instance cannot be started."); @@ -106,13 +88,13 @@ public async Task StartScheduledOrchestrationInstanceAsync(OrchestrationInstance /// public async Task CancelScheduledOrchestrationInstanceAsync(OrchestrationInstanceId id) { - var orchestrationInstance = await _orchestrationInstanceRepository.GetAsync(id).ConfigureAwait(false); + var orchestrationInstance = await _repository.GetAsync(id).ConfigureAwait(false); if (!orchestrationInstance.Lifecycle.IsPendingForScheduledStart()) throw new InvalidOperationException("Orchestration instance cannot be canceled."); // Transition lifecycle orchestrationInstance.Lifecycle.TransitionToTerminated(_clock, OrchestrationInstanceTerminationStates.UserCanceled); - await _unitOfWork.CommitAsync().ConfigureAwait(false); + await _repository.UnitOfWork.CommitAsync().ConfigureAwait(false); } /// @@ -160,8 +142,8 @@ private async Task CreateOrchestrationInstanceAsync /// Read/write access to the orchestration register. /// -public interface IOrchestrationRegister +internal interface IOrchestrationRegister { Task> GetAllByHostNameAsync(string hostName); diff --git a/source/ProcessManager.Core/Application/OrchestrationRegisterExtensions.cs b/source/ProcessManager.Core/Application/Registration/OrchestrationRegisterExtensions.cs similarity index 95% rename from source/ProcessManager.Core/Application/OrchestrationRegisterExtensions.cs rename to source/ProcessManager.Core/Application/Registration/OrchestrationRegisterExtensions.cs index e5aac16bd0..5f138c9958 100644 --- a/source/ProcessManager.Core/Application/OrchestrationRegisterExtensions.cs +++ b/source/ProcessManager.Core/Application/Registration/OrchestrationRegisterExtensions.cs @@ -14,9 +14,9 @@ using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationDescription; -namespace Energinet.DataHub.ProcessManagement.Core.Application; +namespace Energinet.DataHub.ProcessManagement.Core.Application.Registration; -public static class OrchestrationRegisterExtensions +internal static class OrchestrationRegisterExtensions { /// /// Synchronize the orchestration register with the Durable Functions orchestrations for an application host. @@ -58,9 +58,7 @@ public static async Task SynchronizeAsync( && x.Version == hostDescription.Version); if (registerDescription == null || registerDescription.IsEnabled == false) - { await register.RegisterAsync(hostDescription, hostName).ConfigureAwait(false); - } } } } diff --git a/source/ProcessManager.Core/Application/IQueryScheduledOrchestrationInstancesByInstant.cs b/source/ProcessManager.Core/Application/Scheduling/IScheduledOrchestrationInstancesByInstantQuery.cs similarity index 87% rename from source/ProcessManager.Core/Application/IQueryScheduledOrchestrationInstancesByInstant.cs rename to source/ProcessManager.Core/Application/Scheduling/IScheduledOrchestrationInstancesByInstantQuery.cs index ed3d51f24b..b8962637fd 100644 --- a/source/ProcessManager.Core/Application/IQueryScheduledOrchestrationInstancesByInstant.cs +++ b/source/ProcessManager.Core/Application/Scheduling/IScheduledOrchestrationInstancesByInstantQuery.cs @@ -15,9 +15,9 @@ using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; using NodaTime; -namespace Energinet.DataHub.ProcessManagement.Core.Application; +namespace Energinet.DataHub.ProcessManagement.Core.Application.Scheduling; -public interface IQueryScheduledOrchestrationInstancesByInstant +public interface IScheduledOrchestrationInstancesByInstantQuery { /// /// Find scheduled orchestration instances that should be started when comparing to given . diff --git a/source/ProcessManager.Core/Application/IOrchestrationInstanceScheduleManager.cs b/source/ProcessManager.Core/Application/Scheduling/IStartScheduledOrchestrationInstanceCommand.cs similarity index 86% rename from source/ProcessManager.Core/Application/IOrchestrationInstanceScheduleManager.cs rename to source/ProcessManager.Core/Application/Scheduling/IStartScheduledOrchestrationInstanceCommand.cs index c1be747be2..6ec08dbab6 100644 --- a/source/ProcessManager.Core/Application/IOrchestrationInstanceScheduleManager.cs +++ b/source/ProcessManager.Core/Application/Scheduling/IStartScheduledOrchestrationInstanceCommand.cs @@ -14,9 +14,9 @@ using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; -namespace Energinet.DataHub.ProcessManagement.Core.Application; +namespace Energinet.DataHub.ProcessManagement.Core.Application.Scheduling; -public interface IOrchestrationInstanceScheduleManager +public interface IStartScheduledOrchestrationInstanceCommand { /// /// Start a scheduled orchestration instance. diff --git a/source/ProcessManager.Core/Infrastructure/Database/OrchestrationDescriptionEntityConfiguration.cs b/source/ProcessManager.Core/Infrastructure/Database/OrchestrationDescriptionEntityConfiguration.cs index 61bf4683a2..1c47413acf 100644 --- a/source/ProcessManager.Core/Infrastructure/Database/OrchestrationDescriptionEntityConfiguration.cs +++ b/source/ProcessManager.Core/Infrastructure/Database/OrchestrationDescriptionEntityConfiguration.cs @@ -18,7 +18,7 @@ namespace Energinet.DataHub.ProcessManagement.Core.Infrastructure.Database; -public class OrchestrationDescriptionEntityConfiguration : IEntityTypeConfiguration +internal class OrchestrationDescriptionEntityConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { diff --git a/source/ProcessManager.Core/Infrastructure/Database/OrchestrationInstanceEntityConfiguration.cs b/source/ProcessManager.Core/Infrastructure/Database/OrchestrationInstanceEntityConfiguration.cs index 4d7b4e8dbd..f50b1562d0 100644 --- a/source/ProcessManager.Core/Infrastructure/Database/OrchestrationInstanceEntityConfiguration.cs +++ b/source/ProcessManager.Core/Infrastructure/Database/OrchestrationInstanceEntityConfiguration.cs @@ -19,7 +19,7 @@ namespace Energinet.DataHub.ProcessManagement.Core.Infrastructure.Database; -public class OrchestrationInstanceEntityConfiguration : IEntityTypeConfiguration +internal class OrchestrationInstanceEntityConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { diff --git a/source/ProcessManager.Core/Infrastructure/Database/ProcessManagerContext.cs b/source/ProcessManager.Core/Infrastructure/Database/ProcessManagerContext.cs index 7f9bc9e783..7f362fa17c 100644 --- a/source/ProcessManager.Core/Infrastructure/Database/ProcessManagerContext.cs +++ b/source/ProcessManager.Core/Infrastructure/Database/ProcessManagerContext.cs @@ -12,23 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationDescription; using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; using Microsoft.EntityFrameworkCore; namespace Energinet.DataHub.ProcessManagement.Core.Infrastructure.Database; -public class ProcessManagerContext : DbContext +public class ProcessManagerContext( + DbContextOptions options) : + DbContext(options), IUnitOfWork { - public ProcessManagerContext(DbContextOptions options) - : base(options) - { - } - public DbSet OrchestrationDescriptions { get; private set; } public DbSet OrchestrationInstances { get; private set; } + public Task CommitAsync(CancellationToken cancellationToken = default) + { + return SaveChangesAsync(cancellationToken); + } + public override int SaveChanges() { throw new NotSupportedException("Use the async version instead"); diff --git a/source/ProcessManager.Core/Infrastructure/Extensions/DependencyInjection/ProcessManagerExtensions.cs b/source/ProcessManager.Core/Infrastructure/Extensions/DependencyInjection/ProcessManagerExtensions.cs index 5487a0d205..b72236fde2 100644 --- a/source/ProcessManager.Core/Infrastructure/Extensions/DependencyInjection/ProcessManagerExtensions.cs +++ b/source/ProcessManager.Core/Infrastructure/Extensions/DependencyInjection/ProcessManagerExtensions.cs @@ -12,11 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Energinet.DataHub.ProcessManagement.Core.Application; +using Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; +using Energinet.DataHub.ProcessManagement.Core.Application.Registration; +using Energinet.DataHub.ProcessManagement.Core.Application.Scheduling; using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationDescription; using Energinet.DataHub.ProcessManagement.Core.Infrastructure.Database; using Energinet.DataHub.ProcessManagement.Core.Infrastructure.Extensions.Options; using Energinet.DataHub.ProcessManagement.Core.Infrastructure.Orchestration; +using Energinet.DataHub.ProcessManagement.Core.Infrastructure.Registration; using Microsoft.Azure.WebJobs.Extensions.DurableTask; using Microsoft.Azure.WebJobs.Extensions.DurableTask.ContextImplementations; using Microsoft.Azure.WebJobs.Extensions.DurableTask.Options; @@ -66,13 +69,18 @@ public static IServiceCollection AddProcessManagerCore(this IServiceCollection s }); // ProcessManager components using interfaces to restrict access to functionality - // => Scheduler - services.TryAddScoped(); - services.TryAddScoped(); - // => Manager + // => Scheduling + services.TryAddScoped(); + services.TryAddScoped(); + // => Cancellation (manager) + services.TryAddScoped(); + // => Start instance (manager) + services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); - services.TryAddScoped(); + services.TryAddScoped(); + // => Public queries + services.TryAddScoped(); return services; } @@ -122,12 +130,13 @@ public static IServiceCollection AddProcessManagerForOrchestrations( // => Orchestration Descriptions registration during startup services.TryAddTransient>(sp => enabledDescriptionsFactory()); services.TryAddTransient(); - // => Orchestration instances progress - services.TryAddScoped(); - // => Manager + // => Start instance (manager) + services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); - services.TryAddScoped(); + services.TryAddScoped(); + // => Public progress repository + services.TryAddScoped(); return services; } @@ -161,8 +170,9 @@ private static IServiceCollection AddProcessManagerDatabase(this IServiceCollect providerOptionsBuilder.UseNodaTime(); providerOptionsBuilder.EnableRetryOnFailure(); }); - }) - .AddScoped() + }); + + services .AddHealthChecks() .AddDbContextCheck(name: "ProcesManagerDatabase"); diff --git a/source/ProcessManager.Core/Infrastructure/Extensions/Startup/HostExtensions.cs b/source/ProcessManager.Core/Infrastructure/Extensions/Startup/HostExtensions.cs index 69aa92a553..d6523f9a47 100644 --- a/source/ProcessManager.Core/Infrastructure/Extensions/Startup/HostExtensions.cs +++ b/source/ProcessManager.Core/Infrastructure/Extensions/Startup/HostExtensions.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Energinet.DataHub.ProcessManagement.Core.Application; +using Energinet.DataHub.ProcessManagement.Core.Application.Registration; using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationDescription; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -29,7 +29,7 @@ public static class HostExtensions /// /// Register and deregister orchestrations during application startup. /// - public static async Task SynchronizeWithOrchestrationRegisterAsync(this IHost host) + public static async Task SynchronizeWithOrchestrationRegisterAsync(this IHost host, string hostName) { var loggerFactory = host.Services.GetRequiredService(); var logger = loggerFactory.CreateLogger(nameof(SynchronizeWithOrchestrationRegisterAsync)); @@ -40,7 +40,7 @@ public static async Task SynchronizeWithOrchestrationRegisterAsync(this IHost ho var register = host.Services.GetRequiredService(); await register .SynchronizeAsync( - hostName: "ProcessManager.Orchestrations", + hostName: hostName, enabledDescriptions) .ConfigureAwait(false); } diff --git a/source/ProcessManager.Core/Infrastructure/Orchestration/DurableOrchestrationInstanceExecutor.cs b/source/ProcessManager.Core/Infrastructure/Orchestration/DurableOrchestrationInstanceExecutor.cs new file mode 100644 index 0000000000..cb32ba9aa8 --- /dev/null +++ b/source/ProcessManager.Core/Infrastructure/Orchestration/DurableOrchestrationInstanceExecutor.cs @@ -0,0 +1,44 @@ +// Copyright 2020 Energinet DataHub A/S +// +// Licensed under the Apache License, Version 2.0 (the "License2"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; +using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationDescription; +using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; +using Microsoft.Azure.WebJobs.Extensions.DurableTask; + +namespace Energinet.DataHub.ProcessManagement.Core.Infrastructure.Orchestration; + +/// +/// An executor implementation that uses to start +/// Durable Functions orchestration instances. +/// +/// Must be a Durable Task Client that is connected to +/// the same Task Hub as the Durable Functions host containing orchestrations. +internal class DurableOrchestrationInstanceExecutor( + IDurableClient durableClient) : + IOrchestrationInstanceExecutor +{ + private readonly IDurableClient _durableClient = durableClient; + + /// + public Task StartNewOrchestrationInstanceAsync( + OrchestrationDescription orchestrationDescription, + OrchestrationInstance orchestrationInstance) + { + return _durableClient.StartNewAsync( + orchestratorFunctionName: orchestrationDescription.FunctionName, + orchestrationInstance.Id.Value.ToString(), + input: orchestrationInstance.ParameterValue.SerializedParameterValue); + } +} diff --git a/source/ProcessManager.Core/Infrastructure/Orchestration/OrchestrationInstanceRepository.cs b/source/ProcessManager.Core/Infrastructure/Orchestration/OrchestrationInstanceRepository.cs index 72f24e0d5f..f558fc1f4f 100644 --- a/source/ProcessManager.Core/Infrastructure/Orchestration/OrchestrationInstanceRepository.cs +++ b/source/ProcessManager.Core/Infrastructure/Orchestration/OrchestrationInstanceRepository.cs @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Energinet.DataHub.ProcessManagement.Core.Application; +using Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; +using Energinet.DataHub.ProcessManagement.Core.Application.Scheduling; using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; using Energinet.DataHub.ProcessManagement.Core.Infrastructure.Database; using Microsoft.EntityFrameworkCore; @@ -20,14 +21,20 @@ namespace Energinet.DataHub.ProcessManagement.Core.Infrastructure.Orchestration; -public class OrchestrationInstanceRepository : IOrchestrationInstanceRepository, IQueryScheduledOrchestrationInstancesByInstant +/// +/// Read/write access to the orchestration instance repository. +/// +internal class OrchestrationInstanceRepository( + ProcessManagerContext context) : + IOrchestrationInstanceRepository, + IOrchestrationInstanceProgressRepository, + IOrchestrationInstanceQueries, + IScheduledOrchestrationInstancesByInstantQuery { - private readonly ProcessManagerContext _context; + private readonly ProcessManagerContext _context = context; - public OrchestrationInstanceRepository(ProcessManagerContext context) - { - _context = context; - } + /// + public IUnitOfWork UnitOfWork => _context; /// public Task GetAsync(OrchestrationInstanceId id) @@ -45,7 +52,7 @@ public async Task AddAsync(OrchestrationInstance orchestrationInstance) await _context.OrchestrationInstances.AddAsync(orchestrationInstance).ConfigureAwait(false); } - /// + /// public async Task> FindAsync(Instant scheduledToRunBefore) { var query = _context.OrchestrationInstances diff --git a/source/ProcessManager.Core/Infrastructure/Orchestration/OrchestrationRegister.cs b/source/ProcessManager.Core/Infrastructure/Registration/OrchestrationRegister.cs similarity index 90% rename from source/ProcessManager.Core/Infrastructure/Orchestration/OrchestrationRegister.cs rename to source/ProcessManager.Core/Infrastructure/Registration/OrchestrationRegister.cs index 3f2d32b43d..c6fd6e1d89 100644 --- a/source/ProcessManager.Core/Infrastructure/Orchestration/OrchestrationRegister.cs +++ b/source/ProcessManager.Core/Infrastructure/Registration/OrchestrationRegister.cs @@ -12,26 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Energinet.DataHub.ProcessManagement.Core.Application; +using Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; +using Energinet.DataHub.ProcessManagement.Core.Application.Registration; using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationDescription; using Energinet.DataHub.ProcessManagement.Core.Infrastructure.Database; using Microsoft.EntityFrameworkCore; -namespace Energinet.DataHub.ProcessManagement.Core.Infrastructure.Orchestration; +namespace Energinet.DataHub.ProcessManagement.Core.Infrastructure.Registration; /// /// Keep a register of known Durable Functions orchestrations. /// Each orchestration is registered with information by which it is possible /// to communicate with Durable Functions and start a new orchestration instance. /// -public class OrchestrationRegister : IOrchestrationRegister, IOrchestrationRegisterQueries +internal class OrchestrationRegister( + ProcessManagerContext context) : + IOrchestrationRegister, + IOrchestrationRegisterQueries { - private readonly ProcessManagerContext _context; - - public OrchestrationRegister(ProcessManagerContext context) - { - _context = context; - } + private readonly ProcessManagerContext _context = context; /// public Task GetAsync(OrchestrationDescriptionId id) diff --git a/source/ProcessManager.Core/ProcessManager.Core.csproj b/source/ProcessManager.Core/ProcessManager.Core.csproj index 1d50cedd86..9edffe4a78 100644 --- a/source/ProcessManager.Core/ProcessManager.Core.csproj +++ b/source/ProcessManager.Core/ProcessManager.Core.csproj @@ -18,7 +18,7 @@ - + diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023CalculationStepStartActivityV1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023CalculationStepStartActivityV1.cs index de9e884457..29a6d892be 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023CalculationStepStartActivityV1.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023CalculationStepStartActivityV1.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Energinet.DataHub.ProcessManagement.Core.Application; +using Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; using Microsoft.Azure.Functions.Worker; using NodaTime; @@ -21,12 +21,10 @@ namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_023_027. internal class Brs023CalculationStepStartActivityV1( IClock clock, - IOrchestrationInstanceProgressRepository progressRepository, - IUnitOfWork unitOfWork) + IOrchestrationInstanceProgressRepository progressRepository) : ProgressActivityBase( clock, - progressRepository, - unitOfWork) + progressRepository) { [Function(nameof(Brs023CalculationStepStartActivityV1))] public async Task Run( @@ -38,7 +36,7 @@ public async Task Run( var step = orchestrationInstance.Steps.Single(x => x.Sequence == NotifyAggregatedMeasureDataOrchestrationV1.CalculationStepSequence); step.Lifecycle.TransitionToRunning(Clock); - await UnitOfWork.CommitAsync().ConfigureAwait(false); + await ProgressRepository.UnitOfWork.CommitAsync().ConfigureAwait(false); // TODO: For demo purposes; remove when done await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false); diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023CalculationStepTerminateActivityV1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023CalculationStepTerminateActivityV1.cs index 46c8978fca..06e0cc70bd 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023CalculationStepTerminateActivityV1.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023CalculationStepTerminateActivityV1.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Energinet.DataHub.ProcessManagement.Core.Application; +using Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; using Microsoft.Azure.Functions.Worker; using NodaTime; @@ -21,12 +21,10 @@ namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_023_027. internal class Brs023CalculationStepTerminateActivityV1( IClock clock, - IOrchestrationInstanceProgressRepository progressRepository, - IUnitOfWork unitOfWork) + IOrchestrationInstanceProgressRepository progressRepository) : ProgressActivityBase( clock, - progressRepository, - unitOfWork) + progressRepository) { [Function(nameof(Brs023CalculationStepTerminateActivityV1))] public async Task Run( @@ -38,7 +36,7 @@ public async Task Run( var step = orchestrationInstance.Steps.Single(x => x.Sequence == NotifyAggregatedMeasureDataOrchestrationV1.CalculationStepSequence); step.Lifecycle.TransitionToTerminated(Clock, OrchestrationStepTerminationStates.Succeeded); - await UnitOfWork.CommitAsync().ConfigureAwait(false); + await ProgressRepository.UnitOfWork.CommitAsync().ConfigureAwait(false); // TODO: For demo purposes; remove when done await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023EnqueueMessagesStepStartActivityV1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023EnqueueMessagesStepStartActivityV1.cs index eff37e15d2..c627b6c131 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023EnqueueMessagesStepStartActivityV1.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023EnqueueMessagesStepStartActivityV1.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Energinet.DataHub.ProcessManagement.Core.Application; +using Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; using Microsoft.Azure.Functions.Worker; using NodaTime; @@ -21,12 +21,10 @@ namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_023_027. internal class Brs023EnqueueMessagesStepStartActivityV1( IClock clock, - IOrchestrationInstanceProgressRepository progressRepository, - IUnitOfWork unitOfWork) + IOrchestrationInstanceProgressRepository progressRepository) : ProgressActivityBase( clock, - progressRepository, - unitOfWork) + progressRepository) { [Function(nameof(Brs023EnqueueMessagesStepStartActivityV1))] public async Task Run( @@ -40,7 +38,7 @@ public async Task Run( if (!step.IsSkipped()) { step.Lifecycle.TransitionToRunning(Clock); - await UnitOfWork.CommitAsync().ConfigureAwait(false); + await ProgressRepository.UnitOfWork.CommitAsync().ConfigureAwait(false); // TODO: For demo purposes; remove when done await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false); diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023EnqueueMessagesStepTerminateActivityV1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023EnqueueMessagesStepTerminateActivityV1.cs index b338f7e9b0..92d922358a 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023EnqueueMessagesStepTerminateActivityV1.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023EnqueueMessagesStepTerminateActivityV1.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Energinet.DataHub.ProcessManagement.Core.Application; +using Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; using Microsoft.Azure.Functions.Worker; using NodaTime; @@ -21,12 +21,10 @@ namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_023_027. internal class Brs023EnqueueMessagesStepTerminateActivityV1( IClock clock, - IOrchestrationInstanceProgressRepository progressRepository, - IUnitOfWork unitOfWork) + IOrchestrationInstanceProgressRepository progressRepository) : ProgressActivityBase( clock, - progressRepository, - unitOfWork) + progressRepository) { [Function(nameof(Brs023EnqueueMessagesStepTerminateActivityV1))] public async Task Run( @@ -40,7 +38,7 @@ public async Task Run( if (!step.IsSkipped()) { step.Lifecycle.TransitionToTerminated(Clock, OrchestrationStepTerminationStates.Succeeded); - await UnitOfWork.CommitAsync().ConfigureAwait(false); + await ProgressRepository.UnitOfWork.CommitAsync().ConfigureAwait(false); // TODO: For demo purposes; remove when done await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023OrchestrationInitializeActivityV1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023OrchestrationInitializeActivityV1.cs index 27b99d42bc..914e5bfffd 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023OrchestrationInitializeActivityV1.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023OrchestrationInitializeActivityV1.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Energinet.DataHub.ProcessManagement.Core.Application; +using Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; using Microsoft.Azure.Functions.Worker; using NodaTime; @@ -25,12 +25,10 @@ namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_023_027. /// internal class Brs023OrchestrationInitializeActivityV1( IClock clock, - IOrchestrationInstanceProgressRepository progressRepository, - IUnitOfWork unitOfWork) + IOrchestrationInstanceProgressRepository progressRepository) : ProgressActivityBase( clock, - progressRepository, - unitOfWork) + progressRepository) { [Function(nameof(Brs023OrchestrationInitializeActivityV1))] public async Task Run( @@ -41,7 +39,7 @@ public async Task Run( .ConfigureAwait(false); orchestrationInstance.Lifecycle.TransitionToRunning(Clock); - await UnitOfWork.CommitAsync().ConfigureAwait(false); + await ProgressRepository.UnitOfWork.CommitAsync().ConfigureAwait(false); // TODO: For demo purposes; remove when done await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023OrchestrationTerminateActivityV1.cs b/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023OrchestrationTerminateActivityV1.cs index c10402765c..43560cd96f 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023OrchestrationTerminateActivityV1.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/Brs023OrchestrationTerminateActivityV1.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Energinet.DataHub.ProcessManagement.Core.Application; +using Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; using Microsoft.Azure.Functions.Worker; using NodaTime; @@ -25,12 +25,10 @@ namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_023_027. /// internal class Brs023OrchestrationTerminateActivityV1( IClock clock, - IOrchestrationInstanceProgressRepository progressRepository, - IUnitOfWork unitOfWork) + IOrchestrationInstanceProgressRepository progressRepository) : ProgressActivityBase( clock, - progressRepository, - unitOfWork) + progressRepository) { [Function(nameof(Brs023OrchestrationTerminateActivityV1))] public async Task Run( @@ -41,7 +39,7 @@ public async Task Run( .ConfigureAwait(false); orchestrationInstance.Lifecycle.TransitionToTerminated(Clock, OrchestrationInstanceTerminationStates.Succeeded); - await UnitOfWork.CommitAsync().ConfigureAwait(false); + await ProgressRepository.UnitOfWork.CommitAsync().ConfigureAwait(false); // TODO: For demo purposes; remove when done await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/ProgressActivityBase.cs b/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/ProgressActivityBase.cs index 1ac19e0c35..cbec8cd11e 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/ProgressActivityBase.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/Activities/ProgressActivityBase.cs @@ -12,19 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Energinet.DataHub.ProcessManagement.Core.Application; +using Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; using NodaTime; namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_023_027.V1.Activities; internal abstract class ProgressActivityBase( IClock clock, - IOrchestrationInstanceProgressRepository progressRepository, - IUnitOfWork unitOfWork) + IOrchestrationInstanceProgressRepository progressRepository) { protected IClock Clock { get; } = clock; protected IOrchestrationInstanceProgressRepository ProgressRepository { get; } = progressRepository; - - protected IUnitOfWork UnitOfWork { get; } = unitOfWork; } diff --git a/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/NotifyAggregatedMeasureDataHandler.cs b/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/NotifyAggregatedMeasureDataHandler.cs index 98f770f4df..a97a18161e 100644 --- a/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/NotifyAggregatedMeasureDataHandler.cs +++ b/source/ProcessManager.Orchestrations/Processes/BRS_023_027/V1/NotifyAggregatedMeasureDataHandler.cs @@ -12,28 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Energinet.DataHub.ProcessManagement.Core.Application; +using Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; using Energinet.DataHub.ProcessManager.Api.Model; using Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_023_027.V1.Model; -using Microsoft.Extensions.Logging; -using NodaTime; using NodaTime.Extensions; namespace Energinet.DataHub.ProcessManager.Orchestrations.Processes.BRS_023_027.V1; -public class NotifyAggregatedMeasureDataHandler( - ILogger logger, - IClock clock, - IOrchestrationInstanceRepository repository, - IUnitOfWork unitOfWork, - IOrchestrationInstanceManager manager) +internal class NotifyAggregatedMeasureDataHandler( + IStartOrchestrationInstanceCommands manager) { - private readonly ILogger _logger = logger; - private readonly IClock _clock = clock; - private readonly IOrchestrationInstanceRepository _repository = repository; - private readonly IUnitOfWork _unitOfWork = unitOfWork; - private readonly IOrchestrationInstanceManager _manager = manager; + private readonly IStartOrchestrationInstanceCommands _manager = manager; public async Task ScheduleNewCalculationAsync( ScheduleOrchestrationInstanceDto dto) diff --git a/source/ProcessManager.Orchestrations/Program.cs b/source/ProcessManager.Orchestrations/Program.cs index f784ced92c..f32116b0a3 100644 --- a/source/ProcessManager.Orchestrations/Program.cs +++ b/source/ProcessManager.Orchestrations/Program.cs @@ -80,5 +80,5 @@ }) .Build(); -await host.SynchronizeWithOrchestrationRegisterAsync().ConfigureAwait(false); +await host.SynchronizeWithOrchestrationRegisterAsync("ProcessManager.Orchestrations").ConfigureAwait(false); await host.RunAsync().ConfigureAwait(false); diff --git a/source/ProcessManager/Api/CancelScheduledOrchestrationInstanceTrigger.cs b/source/ProcessManager/Api/CancelScheduledOrchestrationInstanceTrigger.cs index 6f306e7b5c..6e8f465888 100644 --- a/source/ProcessManager/Api/CancelScheduledOrchestrationInstanceTrigger.cs +++ b/source/ProcessManager/Api/CancelScheduledOrchestrationInstanceTrigger.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Energinet.DataHub.ProcessManagement.Core.Application; +using Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -23,10 +23,10 @@ namespace Energinet.DataHub.ProcessManager.Api; internal class CancelScheduledOrchestrationInstanceTrigger( ILogger logger, - IOrchestrationInstanceManager manager) + ICancelScheduledOrchestrationInstanceCommand command) { private readonly ILogger _logger = logger; - private readonly IOrchestrationInstanceManager _manager = manager; + private readonly ICancelScheduledOrchestrationInstanceCommand _command = command; /// /// Cancel a scheduled orchestration instance. @@ -41,7 +41,7 @@ public async Task Run( Guid id, FunctionContext executionContext) { - await _manager + await _command .CancelScheduledOrchestrationInstanceAsync(new OrchestrationInstanceId(id)) .ConfigureAwait(false); diff --git a/source/ProcessManager/Api/GetOrchestrationInstanceTrigger.cs b/source/ProcessManager/Api/GetOrchestrationInstanceTrigger.cs index f0850b2adc..baeff84f5b 100644 --- a/source/ProcessManager/Api/GetOrchestrationInstanceTrigger.cs +++ b/source/ProcessManager/Api/GetOrchestrationInstanceTrigger.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Energinet.DataHub.ProcessManagement.Core.Application; +using Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; using Energinet.DataHub.ProcessManager.Api.Mappers; using Microsoft.AspNetCore.Http; @@ -24,10 +24,10 @@ namespace Energinet.DataHub.ProcessManager.Api; internal class GetOrchestrationInstanceTrigger( ILogger logger, - IOrchestrationInstanceRepository repository) + IOrchestrationInstanceQueries queries) { private readonly ILogger _logger = logger; - private readonly IOrchestrationInstanceRepository _repository = repository; + private readonly IOrchestrationInstanceQueries _queries = queries; /// /// Get orchestration instance. @@ -42,7 +42,7 @@ public async Task Run( Guid id, FunctionContext executionContext) { - var orchestrationInstance = await _repository + var orchestrationInstance = await _queries .GetAsync(new OrchestrationInstanceId(id)) .ConfigureAwait(false); diff --git a/source/ProcessManager/Api/SearchOrchestrationInstancesTrigger.cs b/source/ProcessManager/Api/SearchOrchestrationInstancesTrigger.cs index 329fc634f2..90cb81137e 100644 --- a/source/ProcessManager/Api/SearchOrchestrationInstancesTrigger.cs +++ b/source/ProcessManager/Api/SearchOrchestrationInstancesTrigger.cs @@ -13,7 +13,7 @@ // limitations under the License. using System.Globalization; -using Energinet.DataHub.ProcessManagement.Core.Application; +using Energinet.DataHub.ProcessManagement.Core.Application.Orchestration; using Energinet.DataHub.ProcessManagement.Core.Domain.OrchestrationInstance; using Energinet.DataHub.ProcessManager.Api.Mappers; using Microsoft.AspNetCore.Http; @@ -26,10 +26,10 @@ namespace Energinet.DataHub.ProcessManager.Api; internal class SearchOrchestrationInstancesTrigger( ILogger logger, - IOrchestrationInstanceRepository repository) + IOrchestrationInstanceQueries queries) { private readonly ILogger _logger = logger; - private readonly IOrchestrationInstanceRepository _repository = repository; + private readonly IOrchestrationInstanceQueries _queries = queries; [Function(nameof(SearchOrchestrationInstancesTrigger))] public async Task Run( @@ -62,7 +62,7 @@ public async Task Run( ? Instant.FromDateTimeOffset(terminatedAtOrEarlierResult) : (Instant?)null; - var orchestrationInstances = await _repository + var orchestrationInstances = await _queries .SearchAsync(name, version, lifecycleState, terminationState, startedAtOrLater, terminatedAtOrEarlier) .ConfigureAwait(false); diff --git a/source/ProcessManager/Scheduler/SchedulerHandler.cs b/source/ProcessManager/Scheduler/SchedulerHandler.cs index 1a7d94e130..c5daaab23e 100644 --- a/source/ProcessManager/Scheduler/SchedulerHandler.cs +++ b/source/ProcessManager/Scheduler/SchedulerHandler.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Energinet.DataHub.ProcessManagement.Core.Application; +using Energinet.DataHub.ProcessManagement.Core.Application.Scheduling; using Microsoft.Extensions.Logging; using NodaTime; @@ -21,13 +21,13 @@ namespace Energinet.DataHub.ProcessManager.Scheduler; public class SchedulerHandler( ILogger logger, IClock clock, - IQueryScheduledOrchestrationInstancesByInstant query, - IOrchestrationInstanceScheduleManager manager) + IScheduledOrchestrationInstancesByInstantQuery query, + IStartScheduledOrchestrationInstanceCommand command) { private readonly ILogger _logger = logger; private readonly IClock _clock = clock; - private readonly IQueryScheduledOrchestrationInstancesByInstant _query = query; - private readonly IOrchestrationInstanceScheduleManager _manager = manager; + private readonly IScheduledOrchestrationInstancesByInstantQuery _query = query; + private readonly IStartScheduledOrchestrationInstanceCommand _command = command; public async Task StartScheduledOrchestrationInstancesAsync() { @@ -40,7 +40,7 @@ public async Task StartScheduledOrchestrationInstancesAsync() { try { - await _manager + await _command .StartScheduledOrchestrationInstanceAsync(orchestrationInstance.Id) .ConfigureAwait(false); } diff --git a/source/SubsystemTests/Drivers/EdiDatabaseDriver.cs b/source/SubsystemTests/Drivers/EdiDatabaseDriver.cs index 4de47f7c7b..89c62b73ac 100644 --- a/source/SubsystemTests/Drivers/EdiDatabaseDriver.cs +++ b/source/SubsystemTests/Drivers/EdiDatabaseDriver.cs @@ -274,6 +274,21 @@ WHERE B.DequeuedAt IS NOT NULL return dequeuedMessagesCount; } + internal async Task CountEnqueuedMessagesForCalculationAsync(Guid calculationId) + { + await using var connection = new SqlConnection(_connectionString); + + await connection.OpenAsync(); + + var enqueuedMessagesCount = await connection.ExecuteScalarAsync( + sql: @"SELECT COUNT(B.[Id]) FROM [Bundles] B + INNER JOIN [OutgoingMessages] OM ON B.[Id] = OM.[AssignedBundleId] + WHERE OM.[CalculationId] = @CalculationId", + param: new { CalculationId = calculationId, }); + + return enqueuedMessagesCount; + } + private async Task GetProcessIdAsync(SqlCommand command, CancellationToken cancellationToken) { await using var connection = new SqlConnection(_connectionString); diff --git a/source/SubsystemTests/LoadTest/LoadTestFixture.cs b/source/SubsystemTests/LoadTest/LoadTestFixture.cs index 54c3a08f40..7a5edf40d8 100644 --- a/source/SubsystemTests/LoadTest/LoadTestFixture.cs +++ b/source/SubsystemTests/LoadTest/LoadTestFixture.cs @@ -18,6 +18,8 @@ using Energinet.DataHub.Core.FunctionApp.TestCommon.Configuration; using Energinet.DataHub.EDI.B2BApi.AppTests.DurableTask; using Energinet.DataHub.EDI.SubsystemTests.Drivers; +using Microsoft.ApplicationInsights; +using Microsoft.ApplicationInsights.Extensibility; using Microsoft.Azure.WebJobs.Extensions.DurableTask; using Microsoft.Extensions.Configuration; @@ -61,6 +63,11 @@ public LoadTestFixture() configuration, "LOAD_TEST_CALCULATION_ID"); + MinimumEnqueuedMessagesCount = GetConfigurationValue( + configuration, + "MINIMUM_ENQUEUED_MESSAGES_COUNT", + defaultValue: 0); + MinimumDequeuedMessagesCount = GetConfigurationValue( configuration, "MINIMUM_DEQUEUED_MESSAGES_COUNT", @@ -73,18 +80,27 @@ public LoadTestFixture() _durableTaskManager = new DurableTaskManager( "OrchestrationsStorageConnectionString", GetConfigurationValue(configuration, "func-edi-api-taskhub-storage-connection-string")); + + var credential = new DefaultAzureCredential(); + var telemetryConfiguration = TelemetryConfiguration.CreateDefault(); + telemetryConfiguration.SetAzureTokenCredential(credential); + TelemetryClient = new TelemetryClient(telemetryConfiguration); } internal EdiInboxClient EdiInboxClient { get; } internal Guid LoadTestCalculationId { get; } + internal int MinimumEnqueuedMessagesCount { get; } + internal int MinimumDequeuedMessagesCount { get; } internal IntegrationEventPublisher IntegrationEventPublisher { get; } internal string DatabaseConnectionString { get; } + internal TelemetryClient TelemetryClient { get; } + [NotNull] internal IDurableClient? DurableClient { get; private set; } diff --git a/source/SubsystemTests/LoadTest/LoadTestHelper.cs b/source/SubsystemTests/LoadTest/LoadTestHelper.cs index 08e7f7e6ec..9a488154d9 100644 --- a/source/SubsystemTests/LoadTest/LoadTestHelper.cs +++ b/source/SubsystemTests/LoadTest/LoadTestHelper.cs @@ -16,6 +16,7 @@ using Energinet.DataHub.EDI.SubsystemTests.Drivers; using Energinet.DataHub.EDI.SubsystemTests.Dsl; using FluentAssertions; +using FluentAssertions.Execution; using Nito.AsyncEx; using NodaTime; using Xunit.Abstractions; @@ -35,6 +36,8 @@ namespace Energinet.DataHub.EDI.SubsystemTests.LoadTest; [SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "Test class")] public sealed class LoadTestHelper : IClassFixture { + private const string EnqueuedAmountMetric = "EnqueuedAmount"; + private const string DequeuedAmountMetric = "DequeuedAmount"; private readonly LoadTestFixture _fixture; private readonly ITestOutputHelper _logger; private readonly EdiDriver _ediDriver; @@ -69,13 +72,20 @@ await CalculationCompletedDsl.StartEnqueueMessagesOrchestration( [Fact] public async Task After_load_test() { - await _ediDriver.StopOrchestrationForCalculationAsync( - calculationId: _fixture.LoadTestCalculationId, - createdAfter: SystemClock.Instance.GetCurrentInstant().Minus(Duration.FromHours(1))); + var enqueuedMessagesCount = await _ediDatabaseDriver.CountEnqueuedMessagesForCalculationAsync(_fixture.LoadTestCalculationId); + _logger.WriteLine($"Enqueued messages count: {enqueuedMessagesCount} (CalculationId={_fixture.LoadTestCalculationId})"); var dequeuedMessagesCount = await _ediDatabaseDriver.CountDequeuedMessagesForCalculationAsync(_fixture.LoadTestCalculationId); _logger.WriteLine($"Dequeued messages count: {dequeuedMessagesCount} (CalculationId={_fixture.LoadTestCalculationId})"); + _fixture.TelemetryClient.GetMetric(EnqueuedAmountMetric).TrackValue(enqueuedMessagesCount); + _fixture.TelemetryClient.GetMetric(DequeuedAmountMetric).TrackValue(dequeuedMessagesCount); + + using var scope = new AssertionScope(); + enqueuedMessagesCount.Should().BeGreaterThanOrEqualTo( + _fixture.MinimumEnqueuedMessagesCount, + $"because the system should be performant enough to enqueue at least {_fixture.MinimumEnqueuedMessagesCount} messages during the load test"); + dequeuedMessagesCount.Should().BeGreaterThanOrEqualTo( _fixture.MinimumDequeuedMessagesCount, $"because the system should be performant enough to dequeue at least {_fixture.MinimumDequeuedMessagesCount} messages during the load test"); diff --git a/source/SubsystemTests/SubsystemTests.csproj b/source/SubsystemTests/SubsystemTests.csproj index 830157e114..81894bbeb2 100644 --- a/source/SubsystemTests/SubsystemTests.csproj +++ b/source/SubsystemTests/SubsystemTests.csproj @@ -74,7 +74,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -83,13 +83,13 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/SystemTests/SystemTests.csproj b/source/SystemTests/SystemTests.csproj index 4a0cbdafe6..ecdfe75ffe 100644 --- a/source/SystemTests/SystemTests.csproj +++ b/source/SystemTests/SystemTests.csproj @@ -14,12 +14,12 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/source/Tests/CimMessageAdapter/Messages/MeteredDataForMeasurementPointEbixMessageParserTests/MessageParserTests.cs b/source/Tests/CimMessageAdapter/Messages/MeteredDataForMeasurementPointMessageParserTests/MessageParserTests.cs similarity index 89% rename from source/Tests/CimMessageAdapter/Messages/MeteredDataForMeasurementPointEbixMessageParserTests/MessageParserTests.cs rename to source/Tests/CimMessageAdapter/Messages/MeteredDataForMeasurementPointMessageParserTests/MessageParserTests.cs index 8926c8ce51..eefc14d39a 100644 --- a/source/Tests/CimMessageAdapter/Messages/MeteredDataForMeasurementPointEbixMessageParserTests/MessageParserTests.cs +++ b/source/Tests/CimMessageAdapter/Messages/MeteredDataForMeasurementPointMessageParserTests/MessageParserTests.cs @@ -17,15 +17,14 @@ using Energinet.DataHub.EDI.IncomingMessages.Domain; using Energinet.DataHub.EDI.IncomingMessages.Domain.Validation.ValidationErrors; using Energinet.DataHub.EDI.IncomingMessages.Infrastructure.MessageParsers; -using Energinet.DataHub.EDI.IncomingMessages.Infrastructure.MessageParsers.MeteredDateForMeasurementPointParsers.Ebix; +using Energinet.DataHub.EDI.IncomingMessages.Infrastructure.MessageParsers.MeteredDateForMeasurementPointParsers; using Energinet.DataHub.EDI.IncomingMessages.Infrastructure.Schemas.Ebix; using Energinet.DataHub.EDI.IncomingMessages.Interfaces.Models; using FluentAssertions; using FluentAssertions.Execution; -using Microsoft.Extensions.Logging; using Xunit; -namespace Energinet.DataHub.EDI.Tests.CimMessageAdapter.Messages.MeteredDataForMeasurementPointEbixMessageParserTests; +namespace Energinet.DataHub.EDI.Tests.CimMessageAdapter.Messages.MeteredDataForMeasurementPointMessageParserTests; public sealed class MessageParserTests { @@ -35,10 +34,10 @@ public sealed class MessageParserTests private static readonly string SubPath = $"{Path.DirectorySeparatorChar}MeteredDataForMeasurementPoint{Path.DirectorySeparatorChar}"; - private readonly MarketMessageParser _marketMessageParser = new( - [ - new MeteredDataForMeasurementPointEbixMessageParser(new EbixSchemaProvider(), new Logger(new LoggerFactory())), - ]); + private readonly Dictionary _marketMessageParser = new() + { + { DocumentFormat.Ebix, new EbixMessageParser(new EbixSchemaProvider()) }, + }; public static TheoryData CreateMessagesWithSingleAndMultipleTransactions() { @@ -67,10 +66,8 @@ public static TheoryData CreateBadMessages() [MemberData(nameof(CreateMessagesWithSingleAndMultipleTransactions))] public async Task Successfully_parsed(DocumentFormat format, Stream message) { - var result = await _marketMessageParser.ParseAsync( + var result = await _marketMessageParser.GetValueOrDefault(format)!.ParseAsync( new IncomingMarketMessageStream(message), - format, - IncomingDocumentType.MeteredDataForMeasurementPoint, CancellationToken.None); using var assertionScope = new AssertionScope(); @@ -122,10 +119,8 @@ public async Task Successfully_parsed(DocumentFormat format, Stream message) [MemberData(nameof(CreateBadMessages))] public async Task Messages_with_errors(DocumentFormat format, Stream message, string expectedError) { - var result = await _marketMessageParser.ParseAsync( + var result = await _marketMessageParser.GetValueOrDefault(format)!.ParseAsync( new IncomingMarketMessageStream(message), - format, - IncomingDocumentType.MeteredDataForMeasurementPoint, CancellationToken.None); result.Success.Should().BeFalse(); diff --git a/source/Tests/Tests.csproj b/source/Tests/Tests.csproj index a5b5a517fb..8824e4ce63 100644 --- a/source/Tests/Tests.csproj +++ b/source/Tests/Tests.csproj @@ -22,14 +22,14 @@ limitations under the License. - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive