Skip to content

Commit

Permalink
updated
Browse files Browse the repository at this point in the history
  • Loading branch information
MadsDue committed Nov 7, 2024
1 parent 14d0a86 commit 9e40066
Show file tree
Hide file tree
Showing 7 changed files with 30 additions and 215 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public async Task<IncomingMarketMessageParserResult> 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 await parser.ParseAsync(marketMessage, cancellationToken).ConfigureAwait(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<MeteredDataForMeasurementPointEbixMessageParser> logger) : IMarketMessageParser
public class MeteredDataForMeasurementPointEbixMessageParser(MeteredDataForMeasurementPointMessageParser messageParser) : IMarketMessageParser
{
private const string RootPayloadElementName = "DK_MeteredDataTimeSeries";
private readonly EbixSchemaProvider _schemaProvider = schemaProvider;
private readonly ILogger<MeteredDataForMeasurementPointEbixMessageParser> _logger = logger;
private readonly MeteredDataForMeasurementPointMessageParser _messageParser = messageParser;

public DocumentFormat HandledFormat => DocumentFormat.Ebix;

public IncomingDocumentType DocumentType => IncomingDocumentType.MeteredDataForMeasurementPoint;

private Collection<ValidationError> Errors { get; } = [];

public async Task<IncomingMarketMessageParserResult> 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<IncomingMarketMessageParserResult> 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<XmlSchema>(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.ParseEbixXmlAsync(incomingMarketMessageStream, cancellationToken).ConfigureAwait(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ public partial class MeteredDataForMeasurementPointMessageParser : IMessageParse

public Task<IncomingMarketMessageParserResult> ParseXmlAsync(IIncomingMarketMessageStream incomingMarketMessageStream, CancellationToken cancellationToken)
{
throw new NotImplementedException();
throw new NotSupportedException();
}

public Task<IncomingMarketMessageParserResult> ParseJsonAsync(IIncomingMarketMessageStream incomingMarketMessageStream, CancellationToken cancellationToken)
{
throw new NotImplementedException();
throw new NotSupportedException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public async Task When_ParsingMessageOfDocumentTypeAndFormat_Then_ExpectedMessag
// Assert
if (_unsupportedCombinationsOfIncomingDocumentTypeAndDocumentFormat.Contains((incomingDocumentType, documentFormat)))
{
await act.Should().ThrowAsync<InvalidOperationException>("because this combination is not supported");
await act.Should().ThrowAsync<NotSupportedException>("because this combination is not supported");
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
// limitations under the License.

using System.Xml.Linq;
using Energinet.DataHub.BuildingBlocks.Tests.TestDoubles;
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.MessageParsers;
using Energinet.DataHub.EDI.IncomingMessages.Infrastructure.MessageParsers.MeteredDateForMeasurementPointParsers;
using Energinet.DataHub.EDI.IncomingMessages.Infrastructure.MessageParsers.MeteredDateForMeasurementPointParsers.Ebix;
using Energinet.DataHub.EDI.IncomingMessages.Infrastructure.Schemas.Ebix;
using Energinet.DataHub.EDI.IncomingMessages.Interfaces.Models;
Expand All @@ -25,7 +27,7 @@
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
{
Expand All @@ -36,9 +38,14 @@ public sealed class MessageParserTests
$"{Path.DirectorySeparatorChar}MeteredDataForMeasurementPoint{Path.DirectorySeparatorChar}";

private readonly MarketMessageParser _marketMessageParser = new(
[
new MeteredDataForMeasurementPointEbixMessageParser(new EbixSchemaProvider(), new Logger<MeteredDataForMeasurementPointEbixMessageParser>(new LoggerFactory())),
]);
new List<IMarketMessageParser>(),
new Dictionary<IncomingDocumentType, IMessageParser>()
{
{
IncomingDocumentType.MeteredDataForMeasurementPoint, new MeteredDataForMeasurementPointMessageParser(new EbixSchemaProvider(), new Logger<MeteredDataForMeasurementPointEbixMessageParser>(new LoggerFactory()))
},
},
new FeatureFlagManagerStub());

public static TheoryData<DocumentFormat, Stream> CreateMessagesWithSingleAndMultipleTransactions()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

using System.Text;
using System.Xml.Linq;
using Energinet.DataHub.BuildingBlocks.Tests.TestDoubles;
using Energinet.DataHub.EDI.B2CWebApi.Factories;
using Energinet.DataHub.EDI.BuildingBlocks.Domain.DataHub;
using Energinet.DataHub.EDI.BuildingBlocks.Domain.Models;
Expand Down Expand Up @@ -44,11 +45,13 @@ public sealed class MessageParserTests
$"{Path.DirectorySeparatorChar}aggregatedmeasure{Path.DirectorySeparatorChar}";

private readonly MarketMessageParser _marketMessageParser = new(
[
new AggregatedMeasureDataXmlMessageParser(new CimXmlSchemaProvider(new CimXmlSchemas())),
new AggregatedMeasureDataJsonMessageParser(new JsonSchemaProvider(new CimJsonSchemas())),
new AggregatedMeasureDataB2CJsonMessageParser(new Serializer()),
]);
[
new AggregatedMeasureDataXmlMessageParser(new CimXmlSchemaProvider(new CimXmlSchemas())),
new AggregatedMeasureDataJsonMessageParser(new JsonSchemaProvider(new CimJsonSchemas())),
new AggregatedMeasureDataB2CJsonMessageParser(new Serializer()),
],
new Dictionary<IncomingDocumentType, IMessageParser>(),
new FeatureFlagManagerStub());

public static IEnumerable<object[]> CreateMessagesWithSingleAndMultipleTransactions()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

using System.Text;
using System.Xml.Linq;
using Energinet.DataHub.BuildingBlocks.Tests.TestDoubles;
using Energinet.DataHub.EDI.B2CWebApi.Factories;
using Energinet.DataHub.EDI.B2CWebApi.Models;
using Energinet.DataHub.EDI.BuildingBlocks.Domain.Models;
Expand Down Expand Up @@ -54,7 +55,9 @@ public MessageParserTests()
new WholesaleSettlementXmlMessageParser(new CimXmlSchemaProvider(new CimXmlSchemas())),
new WholesaleSettlementJsonMessageParser(new JsonSchemaProvider(new CimJsonSchemas())),
new WholesaleSettlementB2CJsonMessageParser(_serializer),
]);
],
new Dictionary<IncomingDocumentType, IMessageParser>(),
new FeatureFlagManagerStub());
}

public static IEnumerable<object[]> CreateMessagesWithTwoChargeTypes()
Expand Down

0 comments on commit 9e40066

Please sign in to comment.