// Copyright (C) Stichting Deltares 2018. All rights reserved. // // This file is part of Ringtoets. // // Ringtoets is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // All names, logos, and references to "Deltares" are registered trademarks of // Stichting Deltares and remain full property of Stichting Deltares at all times. // All rights reserved. using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using Core.Common.Base.IO; using log4net; using Ringtoets.Common.Data; using Ringtoets.Common.Data.AssessmentSection; using Ringtoets.Common.Data.Exceptions; using Ringtoets.Common.Data.Hydraulics; using Ringtoets.Common.Data.IllustrationPoints; using Ringtoets.Common.Data.Structures; using Ringtoets.Common.IO.HydraRing; using Ringtoets.Common.Service.IllustrationPoints; using Ringtoets.Common.Service.MessageProviders; using Ringtoets.Common.Service.Properties; using Ringtoets.Common.Service.ValidationRules; using Ringtoets.HydraRing.Calculation.Calculator; using Ringtoets.HydraRing.Calculation.Calculator.Factory; using Ringtoets.HydraRing.Calculation.Data.Input; using Ringtoets.HydraRing.Calculation.Exceptions; using HydraRingGeneralResult = Ringtoets.HydraRing.Calculation.Data.Output.IllustrationPoints.GeneralResult; namespace Ringtoets.Common.Service.Structures { /// /// Service that provides generic logic for performing Hydra-Ring calculations for structures. /// /// The type of the validation rules. /// The structure input type. /// The structure type. /// The general input type. /// The calculation input type. public abstract class StructuresCalculationServiceBase where TStructureValidationRules : IStructuresValidationRulesRegistry, new() where TStructureInput : StructuresInputBase, new() where TStructure : StructureBase where TGeneralInput : class where TCalculationInput : ExceedanceProbabilityCalculationInput { private static readonly ILog log = LogManager.GetLogger(typeof(StructuresCalculationServiceBase)); private readonly IStructuresCalculationMessageProvider messageProvider; private IStructuresCalculator calculator; private bool canceled; /// /// Creates a new instance of . /// /// The object which is used to build log messages. /// Thrown when /// is null. protected StructuresCalculationServiceBase(IStructuresCalculationMessageProvider messageProvider) { if (messageProvider == null) { throw new ArgumentNullException(nameof(messageProvider)); } this.messageProvider = messageProvider; } /// /// Performs validation over the values on the given . Error and status information is logged during /// the execution of the operation. /// /// The for which to validate the values. /// The for which to validate the values. /// true if has no validation errors; false otherwise. /// Thrown when any parameter is null. /// Thrown when an invalid enum value is encountered. /// Thrown when an unsupported enum value is encountered. public static bool Validate(StructuresCalculation calculation, IAssessmentSection assessmentSection) { if (calculation == null) { throw new ArgumentNullException(nameof(calculation)); } if (assessmentSection == null) { throw new ArgumentNullException(nameof(assessmentSection)); } CalculationServiceHelper.LogValidationBegin(); string[] messages = ValidateInput(calculation.InputParameters, assessmentSection); CalculationServiceHelper.LogMessagesAsError(messages); CalculationServiceHelper.LogValidationEnd(); return !messages.Any(); } /// /// Performs a structures calculation based on the supplied and sets /// if the calculation was successful. Error and status information is logged during the execution of the operation. /// /// The that holds all the information required to perform the calculation. /// The general inputs used in the calculations. /// The containing all data /// to perform a hydraulic boundary calculation. /// Preprocessing is disabled when the preprocessor directory equals . /// Thrown when any parameter is null. /// Thrown when the hydraulic boundary database file path /// contains invalid characters. /// Thrown when an unexpected /// enum value is encountered. /// Thrown when: /// /// No settings database file could be found at the location of the hydraulic boundary database file path /// with the same name. /// Unable to open settings database file. /// Unable to read required data from database file. /// /// Thrown when an error occurs while performing the calculation. public void Calculate(StructuresCalculation calculation, TGeneralInput generalInput, HydraulicBoundaryCalculationSettings calculationSettings) { if (calculation == null) { throw new ArgumentNullException(nameof(calculation)); } if (generalInput == null) { throw new ArgumentNullException(nameof(generalInput)); } if (calculationSettings == null) { throw new ArgumentNullException(nameof(calculationSettings)); } TCalculationInput input = CreateInput(calculation.InputParameters, generalInput, calculationSettings.HydraulicBoundaryDatabaseFilePath, !string.IsNullOrEmpty(calculationSettings.PreprocessorDirectory)); calculator = HydraRingCalculatorFactory.Instance.CreateStructuresCalculator( HydraRingCalculationSettingsFactory.CreateSettings(calculationSettings)); string calculationName = calculation.Name; CalculationServiceHelper.LogCalculationBegin(); var exceptionThrown = false; try { PerformCalculation(calculation, input); } catch (HydraRingCalculationException) { if (!canceled) { string lastErrorFileContent = calculator.LastErrorFileContent; string message = string.IsNullOrEmpty(lastErrorFileContent) ? messageProvider.GetCalculationFailedMessage(calculationName) : messageProvider.GetCalculationFailedWithErrorReportMessage(calculationName, lastErrorFileContent); log.Error(message); exceptionThrown = true; throw; } } finally { string lastErrorFileContent = calculator.LastErrorFileContent; bool errorOccurred = CalculationServiceHelper.HasErrorOccurred(canceled, exceptionThrown, lastErrorFileContent); if (errorOccurred) { log.Error(messageProvider.GetCalculationFailedWithErrorReportMessage(calculationName, lastErrorFileContent)); } log.Info(messageProvider.GetCalculationPerformedMessage(calculator.OutputDirectory)); CalculationServiceHelper.LogCalculationEnd(); if (errorOccurred) { throw new HydraRingCalculationException(lastErrorFileContent); } } } /// /// Cancels any currently running structures calculation. /// public void Cancel() { calculator?.Cancel(); canceled = true; } /// /// Creates the input for a structures calculation. /// /// The structure input to create the calculation input for. /// The that is used in the calculation. /// The path to the hydraulic boundary database file. /// Indicator whether to use the preprocessor in the calculation. /// A . /// Thrown when the /// contains invalid characters. /// Thrown when an unexpected /// enum value is encountered. /// Thrown when: /// /// No settings database file could be found at the location of /// with the same name. /// Unable to open settings database file. /// Unable to read required data from database file. /// /// protected abstract TCalculationInput CreateInput(TStructureInput structureInput, TGeneralInput generalInput, string hydraulicBoundaryDatabaseFilePath, bool usePreprocessor); /// /// Performs a structures calculation. /// /// The structures calculation to use. /// The HydraRing calculation input used for the calculation. /// Thrown when an error occurs while performing the calculation. private void PerformCalculation(StructuresCalculation calculation, TCalculationInput calculationInput) { calculator.Calculate(calculationInput); if (canceled || !string.IsNullOrEmpty(calculator.LastErrorFileContent)) { return; } SetOutput(calculation, calculator.ExceedanceProbabilityBeta); } /// /// Sets the calculated output to the calculation object. /// /// The calculation to set the output for. /// The reliability of the calculation. private void SetOutput(StructuresCalculation calculation, double reliability) { GeneralResult generalResult = null; try { generalResult = calculation.InputParameters.ShouldIllustrationPointsBeCalculated ? GetGeneralResult(calculator.IllustrationPointsResult) : null; } catch (ArgumentException e) { log.Warn(string.Format(Resources.CalculationService_Error_in_reading_illustrationPoints_for_CalculationName_0_with_ErrorMessage_1, calculation.Name, e.Message)); } calculation.Output = new StructuresOutput(reliability, generalResult); } /// /// Gets a based on the information /// of . /// /// The to base the /// to create on. /// A , or null if the /// could not be converted. private GeneralResult GetGeneralResult(HydraRingGeneralResult hydraRingGeneralResult) { if (hydraRingGeneralResult == null) { log.Warn(calculator.IllustrationPointsParserErrorMessage); return null; } try { return GeneralResultConverter.ConvertToGeneralResultTopLevelFaultTreeIllustrationPoint(hydraRingGeneralResult); } catch (IllustrationPointConversionException e) { log.Warn(Resources.SetGeneralResult_Converting_IllustrationPointResult_Failed, e); } return null; } /// /// Validates the input. /// /// The input of the calculation. /// The assessment section that holds /// information about the hydraulic boundary database. /// An of validation messages. /// Thrown when an unexpected /// enum value is encountered. private static string[] ValidateInput(TStructureInput input, IAssessmentSection assessmentSection) { var validationResults = new List(); string preprocessorDirectory = assessmentSection.HydraulicBoundaryDatabase.EffectivePreprocessorDirectory(); string databaseFilePathValidationProblem = HydraulicBoundaryDatabaseConnectionValidator.Validate(assessmentSection.HydraulicBoundaryDatabase); if (!string.IsNullOrEmpty(databaseFilePathValidationProblem)) { validationResults.Add(databaseFilePathValidationProblem); } string preprocessorDirectoryValidationProblem = HydraulicBoundaryDatabaseHelper.ValidatePreprocessorDirectory(preprocessorDirectory); if (!string.IsNullOrEmpty(preprocessorDirectoryValidationProblem)) { validationResults.Add(preprocessorDirectoryValidationProblem); } if (validationResults.Any()) { return validationResults.ToArray(); } if (input.HydraulicBoundaryLocation == null) { validationResults.Add(Resources.CalculationService_ValidateInput_No_hydraulic_boundary_location_selected); } if (input.Structure == null) { validationResults.Add(Resources.StructuresCalculationService_ValidateInput_No_Structure_selected); } else { IEnumerable validationRules = new TStructureValidationRules().GetValidationRules(input); foreach (ValidationRule validationRule in validationRules) { validationResults.AddRange(validationRule.Validate()); } } return validationResults.ToArray(); } } }