// Copyright (C) Stichting Deltares 2016. 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.Linq; using Core.Common.Base.IO; using Core.Common.IO.Readers; using log4net; using Ringtoets.Common.Data; using Ringtoets.Common.Data.Calculation; using Ringtoets.Common.Data.DikeProfiles; using Ringtoets.Common.Data.Hydraulics; using Ringtoets.Common.IO.Configurations; using Ringtoets.Common.IO.Properties; using Ringtoets.Common.IO.Readers; namespace Ringtoets.Common.IO.FileImporters { /// /// Base class for importing a calculation configuration from an XML file and storing it on a . /// /// The type of the reader to use for reading the XML file. /// The type of the data read from the XML file by the reader. public abstract class CalculationConfigurationImporter : FileImporterBase where TCalculationConfigurationReader : CalculationConfigurationReader where TReadCalculation : class, IConfigurationItem { private static readonly ILog log = LogManager.GetLogger(typeof(CalculationConfigurationImporter)); /// /// Creates a new instance of . /// /// The path to the XML file to import from. /// The calculation group to update. /// Thrown when any parameter is null. protected CalculationConfigurationImporter(string xmlFilePath, CalculationGroup importTarget) : base(xmlFilePath, importTarget) {} protected override void LogImportCanceledMessage() { log.Info(Resources.CalculationConfigurationImporter_LogImportCanceledMessage_Import_canceled_no_data_read); } protected override bool OnImport() { NotifyProgress(Resources.CalculationConfigurationImporter_ProgressText_Reading_configuration, 1, 3); ReadResult readResult = ReadConfiguration(); if (readResult.CriticalErrorOccurred || Canceled) { return false; } NotifyProgress(Resources.CalculationConfigurationImporter_ProgressText_Validating_imported_data, 2, 3); var parsedCalculationItems = new List(); foreach (IConfigurationItem readItem in readResult.Items) { if (Canceled) { return false; } ICalculationBase parsedItem = ParseReadConfigurationItem(readItem); if (parsedItem != null) { parsedCalculationItems.Add(parsedItem); } } NotifyProgress(Resources.Importer_ProgressText_Adding_imported_data_to_data_model, 3, 3); AddItemsToModel(parsedCalculationItems); return true; } /// /// Creates the reader used for reading the calculation configuration from the provided . /// /// The path to the XML file to import from. /// A reader for reading the calculation configuration. /// Thrown when is invalid. /// Thrown when: /// /// points to a file that does not exist. /// points to a file that does not contain valid XML. /// points to a file that does not pass the schema validation. /// points to a file that does not contain configuration elements. /// /// protected abstract TCalculationConfigurationReader CreateCalculationConfigurationReader(string xmlFilePath); /// /// Parses a calculation from the provided . /// /// The calculation read from XML. /// A parsed calculation instance, or null when something goes wrong while parsing. protected abstract ICalculation ParseReadCalculation(TReadCalculation readCalculation); protected void LogOutOfRangeException(string errorMessage, string calculationName, ArgumentOutOfRangeException e) { LogReadCalculationConversionError($"{errorMessage} {e.Message}", calculationName); } protected void LogReadCalculationConversionError(string message, string calculationName) { log.ErrorFormat(Resources.CalculationConfigurationImporter_ValidateCalculation_ErrorMessage_0_Calculation_1_skipped, message, calculationName); } /// /// Validate the defined wave reduction parameters in combination with a given foreshore profile. /// /// Configuration possibly containing wave reduction parameters. /// The foreshore profile currently assigned to the calculation. /// The name of the calculation which is being validated. /// false when there is an invalid wave reduction parameter defined, true otherwise. /// Thrown when is null. protected bool ValidateWaveReduction(WaveReductionConfiguration waveReduction, ForeshoreProfile foreshoreProfile, string calculationName) { if (calculationName == null) { throw new ArgumentNullException(nameof(calculationName)); } if (foreshoreProfile == null) { if (HasParametersDefined(waveReduction)) { LogReadCalculationConversionError( Resources.CalculationConfigurationImporter_ValidateWaveReduction_No_foreshore_profile_provided, calculationName); return false; } } else if (!foreshoreProfile.Geometry.Any() && waveReduction.UseForeshoreProfile == true) { LogReadCalculationConversionError( string.Format( Resources.ReadForeshoreProfile_ForeshoreProfile_0_has_no_geometry_and_cannot_be_used, foreshoreProfile.Name), calculationName); return false; } return true; } /// /// Tries to find the hydraulic boundary location with the given /// in the . /// /// The name of the location to find. /// Name of the calculation to assign the location to. /// The collection of /// to search in. /// The location with the name equal to /// if there was any. /// true if no is given, or when a location with /// the name was found, false otherwise. /// Thrown when /// or is null. protected bool TryReadHydraulicBoundaryLocation(string locationName, string calculationName, IEnumerable hydraulicBoundaryLocations, out HydraulicBoundaryLocation foundLocation) { if (calculationName == null) { throw new ArgumentNullException(nameof(calculationName)); } if (hydraulicBoundaryLocations == null) { throw new ArgumentNullException(nameof(hydraulicBoundaryLocations)); } foundLocation = null; if (locationName != null) { var location = hydraulicBoundaryLocations.FirstOrDefault(l => l.Name == locationName); if (location == null) { LogReadCalculationConversionError( string.Format( Resources.CalculationConfigurationImporter_ReadHydraulicBoundaryLocation_HydraulicBoundaryLocation_0_does_not_exist, locationName), calculationName); return false; } foundLocation = location; } return true; } /// /// Tries to find the structure with the given /// in the . /// /// The name of the structure to find. /// Name of the calculation to assign the structure to. /// The collection of to search in. /// The structure with the name equal to /// if there was any. /// true if no is given, or when a structure with /// the name was found, false otherwise. /// Thrown when /// or is null. protected bool TryReadStructure(string structureName, string calculationName, IEnumerable structures, out T foundStructure) where T : StructureBase { if (calculationName == null) { throw new ArgumentNullException(nameof(calculationName)); } if (structures == null) { throw new ArgumentNullException(nameof(structures)); } foundStructure = null; if (structureName != null) { T structure = structures.FirstOrDefault(l => l.Name == structureName); if (structure == null) { LogReadCalculationConversionError( string.Format( Resources.CalculationConfigurationImporter_ReadStructure_Structure_0_does_not_exist, structureName), calculationName); return false; } foundStructure = structure; } return true; } /// /// Tries to find the foreshore profile with the given /// in the . /// /// The name of the foreshore profile to find. /// Name of the calculation to assign the foreshore profile to. /// The collection of to search in. /// The foreshore profile with the name equal to /// if there was any. /// true if no is given, or when a foreshore profile with /// the name was found, false otherwise. /// Thrown when /// or is null. protected bool TryReadForeshoreProfile(string foreshoreProfileName, string calculationName, IEnumerable foreshoreProfiles, out ForeshoreProfile foundForeshoreProfile) { if (calculationName == null) { throw new ArgumentNullException(nameof(calculationName)); } if (foreshoreProfiles == null) { throw new ArgumentNullException(nameof(foreshoreProfiles)); } foundForeshoreProfile = null; if (foreshoreProfileName != null) { var foreshoreProfile = foreshoreProfiles.FirstOrDefault(fp => fp.Name == foreshoreProfileName); if (foreshoreProfile == null) { LogReadCalculationConversionError( string.Format( Resources.CalculationConfigurationImporter_ReadForeshoreProfile_ForeshoreProfile_0_does_not_exist, foreshoreProfileName), calculationName); return false; } foundForeshoreProfile = foreshoreProfile; } return true; } private ReadResult ReadConfiguration() { try { return new ReadResult(false) { Items = CreateCalculationConfigurationReader(FilePath).Read().ToList() }; } catch (Exception exception) when (exception is ArgumentException || exception is CriticalFileReadException) { string errorMessage = string.Format(Resources.CalculationConfigurationImporter_HandleCriticalFileReadError_Error_0_no_configuration_imported, exception.Message); log.Error(errorMessage, exception); return new ReadResult(true); } } /// /// Parses the read configuration item. /// /// The read item to parse. /// A parsed calculation item. /// Thrown when the item to parse is not valid. private ICalculationBase ParseReadConfigurationItem(IConfigurationItem readConfigurationItem) { var readCalculationGroup = readConfigurationItem as CalculationGroupConfiguration; if (readCalculationGroup != null) { return ParseReadCalculationGroup(readCalculationGroup); } var readCalculation = readConfigurationItem as TReadCalculation; if (readCalculation != null) { return ParseReadCalculation(readCalculation); } throw new InvalidOperationException("Can't parse item that is not a calculation or calculation group."); } /// /// Parses the read calculation group and it's children. /// /// The calculation group to parse. /// A parsed calculation group. /// Thrown when the one of the children /// to parse is not valid. private CalculationGroup ParseReadCalculationGroup(CalculationGroupConfiguration readCalculationGroup) { var calculationGroup = new CalculationGroup(readCalculationGroup.Name, true); foreach (IConfigurationItem item in readCalculationGroup.Items) { ICalculationBase parsedItem = ParseReadConfigurationItem(item); if (parsedItem != null) { calculationGroup.Children.Add(parsedItem); } } return calculationGroup; } private void AddItemsToModel(IEnumerable parsedCalculationItems) { foreach (ICalculationBase parsedCalculationItem in parsedCalculationItems) { ImportTarget.Children.Add(parsedCalculationItem); } } private static bool HasParametersDefined(WaveReductionConfiguration waveReduction) { return waveReduction != null && (waveReduction.UseBreakWater.HasValue || waveReduction.UseForeshoreProfile.HasValue || waveReduction.BreakWaterHeight.HasValue || waveReduction.BreakWaterType.HasValue); } } }