From c9ca7815d88a85c8e8961ece44f40d5b1b7b87b4 Mon Sep 17 00:00:00 2001 From: Mathieu Guindon Date: Sun, 27 Oct 2024 23:01:09 -0400 Subject: [PATCH] bubble pipeline exceptions up to hangfire orchestrator --- .../Api/Auth/AuthController.cs | 2 +- .../Pipeline/Abstract/IPipeline.cs | 1 + .../Abstract/ISynchronizationPipeline.cs | 5 +- .../ISynchronizationPipelineFactory.cs | 2 +- .../Pipeline/Abstract/PipelineBase.cs | 13 ++--- .../Pipeline/Abstract/PipelineResult.cs | 2 +- .../SynchronizationPipelineFactory.cs | 2 +- .../Sections/SyncXmldoc/SyncMergeBlock.cs | 24 ---------- .../Pipeline/SynchronizeTagsPipeline.cs | 14 +++--- .../Pipeline/SynchronizeXmlPipeline.cs | 14 +++--- .../XmlDoc/XmlDocAnnotation.cs | 47 ++++++++----------- .../XmlDoc/XmlDocInspection.cs | 10 ++-- rubberduckvba.Server/Program.cs | 2 +- .../InstallerDownloadStatsOrchestrator.cs | 21 ++++++++- .../Services/XmldocContentOrchestrator.cs | 21 ++++++++- 15 files changed, 92 insertions(+), 88 deletions(-) delete mode 100644 rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/SyncMergeBlock.cs diff --git a/rubberduckvba.Server/Api/Auth/AuthController.cs b/rubberduckvba.Server/Api/Auth/AuthController.cs index 05269e9..7c07e5a 100644 --- a/rubberduckvba.Server/Api/Auth/AuthController.cs +++ b/rubberduckvba.Server/Api/Auth/AuthController.cs @@ -24,7 +24,7 @@ public class AuthController(IOptions configuration, IOptions> Index() + public ActionResult Index() { var claims = HttpContext.User.Claims.ToDictionary(claim => claim.Type, claim => claim.Value); var hasName = claims.TryGetValue(ClaimTypes.Name, out var name); diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/IPipeline.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/IPipeline.cs index b67671c..693e844 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/IPipeline.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/IPipeline.cs @@ -6,6 +6,7 @@ public interface IPipeline { TContext Context { get; } IPipelineResult Result { get; } + IEnumerable Exceptions { get; } void Start(ITargetBlock entryBlock, TInput input); } \ No newline at end of file diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ISynchronizationPipeline.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ISynchronizationPipeline.cs index 0e6911c..fa5cb16 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ISynchronizationPipeline.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ISynchronizationPipeline.cs @@ -1,8 +1,7 @@ namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -public interface ISynchronizationPipeline +public interface ISynchronizationPipeline : IPipeline where TContext : class { - TContext Context { get; } - Task ExecuteAsync(SyncRequestParameters parameters, CancellationTokenSource tokenSource); + Task> ExecuteAsync(SyncRequestParameters parameters, CancellationTokenSource tokenSource); } diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ISynchronizationPipelineFactory.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ISynchronizationPipelineFactory.cs index 3f0006c..d89ed05 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ISynchronizationPipelineFactory.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/ISynchronizationPipelineFactory.cs @@ -3,5 +3,5 @@ public interface ISynchronizationPipelineFactory where TContext : class { - ISynchronizationPipeline Create(TParameters parameters, CancellationTokenSource tokenSource) where TParameters : IRequestParameters; + ISynchronizationPipeline Create(TParameters parameters, CancellationTokenSource tokenSource) where TParameters : IRequestParameters; } diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/PipelineBase.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/PipelineBase.cs index 6ff0fc0..85830c2 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/PipelineBase.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/PipelineBase.cs @@ -13,14 +13,14 @@ protected PipelineBase(TContext context, CancellationTokenSource tokenSource, IL Context = context; TokenSource = tokenSource; Token = TokenSource.Token; + + Result = Parent?.Result ?? new PipelineResult(); } protected PipelineBase(IPipeline parent, CancellationTokenSource tokenSource, ILogger logger) : this(parent.Context, tokenSource, logger) { - Logger = logger; Parent = parent; - Result = new PipelineResult(); } protected ILogger Logger { get; } @@ -30,6 +30,7 @@ protected PipelineBase(IPipeline parent, CancellationTokenSou public TContext Context { get; } public IPipelineResult Result { get; } + public IEnumerable Exceptions => _sections.SelectMany(section => section.Result.Exceptions).ToList(); public virtual void Start(ITargetBlock entryBlock, TInput input) { @@ -38,12 +39,12 @@ public virtual void Start(ITargetBlock entryBlock, TInput input) entryBlock.Complete(); } - protected IPipeline Parent { get; } + protected IPipeline? Parent { get; } private readonly IList> _sections = new List>(); protected IEnumerable> Sections => _sections; - public void AddSections(SyncRequestParameters parameters, params PipelineSection[] sections) + protected void AddSections(SyncRequestParameters parameters, params PipelineSection[] sections) { foreach (var section in sections) { @@ -57,12 +58,12 @@ protected void LinkSections(ISourceBlock exitBlock, ITargetBlo _links.Add(exitBlock.LinkTo(entryBlock, new DataflowLinkOptions { PropagateCompletion = true })); } - protected void LinkSections(Task exitTask, ITargetBlock successEntryBlock, ITargetBlock faultedEntryBlock = null) + protected void LinkSections(Task exitTask, ITargetBlock successEntryBlock, ITargetBlock? faultedEntryBlock = null) { LinkSections(exitTask, Context, successEntryBlock, faultedEntryBlock); } - protected void LinkSections(Task exitTask, TTarget input, ITargetBlock entryBlock, ITargetBlock faultedEntryBlock = null) + protected void LinkSections(Task exitTask, TTarget input, ITargetBlock entryBlock, ITargetBlock? faultedEntryBlock = null) { Task.WhenAll(exitTask).ContinueWith(t => { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/PipelineResult.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/PipelineResult.cs index be19866..5084ad3 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/PipelineResult.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/PipelineResult.cs @@ -7,7 +7,7 @@ public class PipelineResult : IPipelineResult private readonly ConcurrentBag _exceptions = new ConcurrentBag(); public TResult Result { get; set; } - public IEnumerable Exceptions { get; } + public IEnumerable Exceptions => _exceptions; public void AddException(Exception exception) { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/SynchronizationPipelineFactory.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/SynchronizationPipelineFactory.cs index f688afe..01ae599 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/SynchronizationPipelineFactory.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/Abstract/SynchronizationPipelineFactory.cs @@ -23,7 +23,7 @@ public SynchronizationPipelineFactory(ILogger logger, IRubberduc _markdown = markdown; } - public ISynchronizationPipeline Create(TParameters parameters, CancellationTokenSource tokenSource) where TParameters : IRequestParameters + public ISynchronizationPipeline Create(TParameters parameters, CancellationTokenSource tokenSource) where TParameters : IRequestParameters { return parameters switch { diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/SyncMergeBlock.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/SyncMergeBlock.cs deleted file mode 100644 index 3719937..0000000 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/Sections/SyncXmldoc/SyncMergeBlock.cs +++ /dev/null @@ -1,24 +0,0 @@ -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.Services; - -namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; - -public class SyncMergeBlock : ActionBlockBase -{ - private readonly IStagingServices _staging; - - public SyncMergeBlock(PipelineSection parent, CancellationTokenSource tokenSource, IStagingServices staging, ILogger logger) - : base(parent, tokenSource, logger) - { - _staging = staging; - } - - protected override async Task ActionAsync(SyncContext input) - { - //var results = await _staging.SyncMergeAsync(input.Parameters.RequestId); - //foreach (var result in results) - //{ - // Context.SyncMergeResults.Add(result); - //} - } -} diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/SynchronizeTagsPipeline.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/SynchronizeTagsPipeline.cs index 4e261dd..e135568 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/SynchronizeTagsPipeline.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/SynchronizeTagsPipeline.cs @@ -1,13 +1,11 @@ -using System.Threading.Tasks.Dataflow; - -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Abstract; +using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncTags; +using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Abstract; using rubberduckvba.com.Server.Services; namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline; -public class SynchronizeTagsPipeline : PipelineBase, ISynchronizationPipeline +public class SynchronizeTagsPipeline : PipelineBase, ISynchronizationPipeline { private readonly IRubberduckDbService _content; private readonly IGitHubClientService _github; @@ -23,7 +21,7 @@ public SynchronizeTagsPipeline(IRequestParameters parameters, ILogger logger, IR _staging = staging; } - public async Task ExecuteAsync(SyncRequestParameters parameters, CancellationTokenSource tokenSource) + public async Task> ExecuteAsync(SyncRequestParameters parameters, CancellationTokenSource tokenSource) { if (_isDisposed) { @@ -42,6 +40,8 @@ public async Task ExecuteAsync(SyncRequestParameters parameters, Ca Start(synchronizeTags.InputBlock, parameters); // 04. await completion - return await synchronizeTags.OutputTask.ContinueWith(t => Context, tokenSource.Token); + await synchronizeTags.OutputTask; + + return Result; } } diff --git a/rubberduckvba.Server/ContentSynchronization/Pipeline/SynchronizeXmlPipeline.cs b/rubberduckvba.Server/ContentSynchronization/Pipeline/SynchronizeXmlPipeline.cs index d34f1fa..33eebf0 100644 --- a/rubberduckvba.Server/ContentSynchronization/Pipeline/SynchronizeXmlPipeline.cs +++ b/rubberduckvba.Server/ContentSynchronization/Pipeline/SynchronizeXmlPipeline.cs @@ -1,13 +1,11 @@ -using System.Threading.Tasks.Dataflow; - -using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Abstract; +using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Abstract; using rubberduckvba.com.Server.ContentSynchronization.Pipeline.Sections.SyncXmldoc; +using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Abstract; using rubberduckvba.com.Server.Services; namespace rubberduckvba.com.Server.ContentSynchronization.Pipeline; -public class SynchronizeXmlPipeline : PipelineBase, ISynchronizationPipeline +public class SynchronizeXmlPipeline : PipelineBase, ISynchronizationPipeline { private readonly IRubberduckDbService _content; private readonly IGitHubClientService _github; @@ -25,7 +23,7 @@ public SynchronizeXmlPipeline(IRequestParameters parameters, ILogger logger, IRu _markdown = markdown; } - public async Task ExecuteAsync(SyncRequestParameters parameters, CancellationTokenSource tokenSource) + public async Task> ExecuteAsync(SyncRequestParameters parameters, CancellationTokenSource tokenSource) { if (_isDisposed) { @@ -45,6 +43,8 @@ public async Task ExecuteAsync(SyncRequestParameters parameters, Ca Start(synchronizeFeatureItems.InputBlock!, xmldocRequest); // 04. await completion - return await synchronizeFeatureItems.OutputBlock.Completion.ContinueWith(t => Context); + await synchronizeFeatureItems.OutputBlock.Completion; + + return Result; } } diff --git a/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocAnnotation.cs b/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocAnnotation.cs index 02d12f1..762f116 100644 --- a/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocAnnotation.cs +++ b/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocAnnotation.cs @@ -1,21 +1,21 @@ -using System.Reflection; -using System.Xml.Linq; -using Newtonsoft.Json; +using Newtonsoft.Json; using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Schema; using rubberduckvba.com.Server.Data; +using System.Reflection; +using System.Xml.Linq; namespace rubberduckvba.com.Server.ContentSynchronization.XmlDoc; public class AnnotationProperties { - public AnnotationArgInfo[] Parameters { get; set; } + public AnnotationArgInfo[] Parameters { get; set; } = []; } public class XmlDocAnnotation { public XmlDocAnnotation(string name, XElement node, bool isPreRelease) { - SourceObject = node.Attribute("name").Value.Substring(2).Substring(name.LastIndexOf(".", StringComparison.Ordinal) + 1); + SourceObject = node.Attribute("name")!.Value.Substring(2).Substring(name.LastIndexOf(".", StringComparison.Ordinal) + 1); IsPreRelease = isPreRelease; AnnotationName = name; @@ -84,7 +84,7 @@ private BeforeAndAfterCodeExample[] ParseExamples(XElement node) */ var modules = example.Elements(XmlDocSchema.Annotation.Example.Module.ElementName).AsParallel(); var simpleExamples = modules.Where(m => m.Nodes().OfType().Any()) - .Select((e, i) => new BeforeAndAfterCodeExample(new[] { ExtractCodeModule(e, i) }, modulesAfter: null)) + .Select((e, i) => new BeforeAndAfterCodeExample([ExtractCodeModule(e, i)], modulesAfter: [])) .ToArray(); if (simpleExamples.Length > 0) { @@ -93,7 +93,7 @@ private BeforeAndAfterCodeExample[] ParseExamples(XElement node) } IEnumerable before = Enumerable.Empty(); - IEnumerable after = null; + IEnumerable? after = null; if (modules.Any()) { @@ -110,8 +110,8 @@ private BeforeAndAfterCodeExample[] ParseExamples(XElement node) * * */ - before = modules.Select((e, i) => ExtractCodeModule(e.Element(XmlDocSchema.Annotation.Example.Module.Before.ElementName), i, "(code pane)")); - after = modules.Select((e, i) => ExtractCodeModule(e.Element(XmlDocSchema.Annotation.Example.Module.After.ElementName), i, "(synchronized, hidden attributes shown)")); + before = modules.Select((e, i) => ExtractCodeModule(e.Element(XmlDocSchema.Annotation.Example.Module.Before.ElementName)!, i, "(code pane)")); + after = modules.Select((e, i) => ExtractCodeModule(e.Element(XmlDocSchema.Annotation.Example.Module.After.ElementName)!, i, "(synchronized, hidden attributes shown)")); } if (example.Elements(XmlDocSchema.Annotation.Example.Before.ElementName).Any()) @@ -132,9 +132,9 @@ private BeforeAndAfterCodeExample[] ParseExamples(XElement node) * */ before = example.Elements(XmlDocSchema.Annotation.Example.Before.ElementName) - .Select((e, i) => ExtractCodeModule(e.Element(XmlDocSchema.Annotation.Example.Before.Module.ElementName), i, "(code pane)")); + .Select((e, i) => ExtractCodeModule(e.Element(XmlDocSchema.Annotation.Example.Before.Module.ElementName)!, i, "(code pane)")); after = example.Elements(XmlDocSchema.Annotation.Example.After.ElementName) - .Select((e, i) => ExtractCodeModule(e.Element(XmlDocSchema.Annotation.Example.After.Module.ElementName), i, "(synchronized, hidden attributes shown)")); + .Select((e, i) => ExtractCodeModule(e.Element(XmlDocSchema.Annotation.Example.After.Module.ElementName)!, i, "(synchronized, hidden attributes shown)")); } if (before.Any() && after.Any()) @@ -156,7 +156,7 @@ private BeforeAndAfterCodeExample[] ParseExamples(XElement node) .GetMembers() .Select(m => (m.Name, m.GetCustomAttributes().OfType().SingleOrDefault()?.Description)) .Where(m => m.Description != null) - .ToDictionary(m => m.Description, m => (ExampleModuleType)Enum.Parse(typeof(ExampleModuleType), m.Name, true)); + .ToDictionary(m => m.Description!, m => (ExampleModuleType)Enum.Parse(typeof(ExampleModuleType), m.Name, true)); private static string GetDefaultModuleName(ExampleModuleType type, int index = 1) { @@ -174,12 +174,11 @@ private static string GetDefaultModuleName(ExampleModuleType type, int index = 1 } } - private ExampleModule ExtractCodeModule(XElement cdataParent, int index, string description = null) + private ExampleModule ExtractCodeModule(XElement cdataParent, int index, string? description = null) { var module = cdataParent.AncestorsAndSelf(XmlDocSchema.Annotation.Example.Module.ElementName).Single(); - var moduleType = ModuleTypes.TryGetValue(module.Attribute(XmlDocSchema.Annotation.Example.Module.ModuleTypeAttribute)?.Value, out var type) ? type : ExampleModuleType.Any; - var name = module.Attribute(XmlDocSchema.Annotation.Example.Module.ModuleNameAttribute)?.Value - ?? GetDefaultModuleName(moduleType, index); + var moduleType = ModuleTypes.TryGetValue(module.Attribute(XmlDocSchema.Annotation.Example.Module.ModuleTypeAttribute)?.Value ?? string.Empty, out var type) ? type : ExampleModuleType.Any; + var name = module.Attribute(XmlDocSchema.Annotation.Example.Module.ModuleNameAttribute)?.Value ?? GetDefaultModuleName(moduleType, index); var code = cdataParent.Nodes().OfType().Single().Value; var model = new ExampleModule @@ -194,17 +193,9 @@ private ExampleModule ExtractCodeModule(XElement cdataParent, int index, string } } -public class AnnotationArgInfo +public class AnnotationArgInfo(string name, string type, string description) { - public AnnotationArgInfo() { } - public AnnotationArgInfo(string name, string type, string description) - { - Name = name; - Type = type; - Description = description; - } - - public string Name { get; set; } - public string Type { get; set; } - public string Description { get; set; } + public string Name { get; set; } = name; + public string Type { get; set; } = type; + public string Description { get; set; } = description; } diff --git a/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocInspection.cs b/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocInspection.cs index 0092a5a..7da610b 100644 --- a/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocInspection.cs +++ b/rubberduckvba.Server/ContentSynchronization/XmlDoc/XmlDocInspection.cs @@ -1,12 +1,11 @@ -using System.ComponentModel; +using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Schema; +using rubberduckvba.com.Server.Data; +using rubberduckvba.com.Server.Services; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Reflection; using System.Text.Json; using System.Xml.Linq; -using RubberduckServices; -using rubberduckvba.com.Server.ContentSynchronization.XmlDoc.Schema; -using rubberduckvba.com.Server.Data; -using rubberduckvba.com.Server.Services; namespace rubberduckvba.com.Server.ContentSynchronization.XmlDoc; @@ -29,7 +28,6 @@ public class XmlDocInspection private static readonly string _defaultInspectionType = "CodeQualityIssues"; private readonly IMarkdownFormattingService _markdownService; - private InspectionProperties _properties; public XmlDocInspection(IMarkdownFormattingService markdownService) { _markdownService = markdownService; diff --git a/rubberduckvba.Server/Program.cs b/rubberduckvba.Server/Program.cs index 5baf81c..6446c59 100644 --- a/rubberduckvba.Server/Program.cs +++ b/rubberduckvba.Server/Program.cs @@ -22,7 +22,7 @@ namespace rubberduckvba.com.Server; public class HangfireAuthenticationFilter : IDashboardAuthorizationFilter { - public bool Authorize([NotNull] DashboardContext context) => true; + public bool Authorize([NotNull] DashboardContext context) => context.Request.RemoteIpAddress == context.Request.LocalIpAddress; } public class Program diff --git a/rubberduckvba.Server/Services/InstallerDownloadStatsOrchestrator.cs b/rubberduckvba.Server/Services/InstallerDownloadStatsOrchestrator.cs index f13efd5..a68264c 100644 --- a/rubberduckvba.Server/Services/InstallerDownloadStatsOrchestrator.cs +++ b/rubberduckvba.Server/Services/InstallerDownloadStatsOrchestrator.cs @@ -9,6 +9,25 @@ public class InstallerDownloadStatsOrchestrator(ISynchronizationPipelineFactory< public async Task UpdateContentAsync(TagSyncRequestParameters request, CancellationTokenSource tokenSource) { var pipeline = factory.Create(request, tokenSource); - await pipeline.ExecuteAsync(request, tokenSource); + try + { + await pipeline.ExecuteAsync(request, tokenSource); + } + catch (TaskCanceledException) + { + var exceptions = pipeline.Exceptions.ToList(); + if (exceptions.Count > 0) + { + if (exceptions.Count == 1) + { + throw exceptions[0]; + } + else + { + throw new AggregateException(exceptions); + } + } + throw; + } } } diff --git a/rubberduckvba.Server/Services/XmldocContentOrchestrator.cs b/rubberduckvba.Server/Services/XmldocContentOrchestrator.cs index 813fc15..15b16a4 100644 --- a/rubberduckvba.Server/Services/XmldocContentOrchestrator.cs +++ b/rubberduckvba.Server/Services/XmldocContentOrchestrator.cs @@ -9,6 +9,25 @@ public class XmldocContentOrchestrator(ISynchronizationPipelineFactory 0) + { + if (exceptions.Count == 1) + { + throw exceptions[0]; + } + else + { + throw new AggregateException(exceptions); + } + } + throw; + } } } \ No newline at end of file