// 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.Data; using System.Data.Entity; using System.IO; using System.Linq; using Application.Ringtoets.Storage.Create; using Application.Ringtoets.Storage.DbContext; using Application.Ringtoets.Storage.Exceptions; using Application.Ringtoets.Storage.Properties; using Application.Ringtoets.Storage.Read; using Application.Ringtoets.Storage.Update; using Core.Common.Base.Data; using Core.Common.Base.Storage; using Core.Common.Utils; using Core.Common.Utils.Builders; using Ringtoets.Integration.Data; using UtilsResources = Core.Common.Utils.Properties.Resources; namespace Application.Ringtoets.Storage { /// /// This class interacts with an SQLite database file using the Entity Framework. /// public class StorageSqLite : IStoreProject { private string connectionString; public string FileFilter { get { return Resources.Ringtoets_project_file_filter; } } /// /// Converts to a new in the database. /// /// Path to database file. /// to save. /// Thrown when is null. /// is invalid. /// No file is present at /// at the time a connection is made. /// Thrown when /// /// The database does not contain the table version /// The file was not created. /// Saving the to the database failed. /// The connection to the database file failed. /// /// public void SaveProjectAs(string databaseFilePath, IProject project) { if (!(project is RingtoetsProject)) { throw new ArgumentNullException("project"); } SafeOverwriteFileHelper overwriteHelper = GetSafeOverwriteFileHelper(databaseFilePath); if (overwriteHelper != null) { try { SetConnectionToNewFile(databaseFilePath); SaveProjectInDatabase(databaseFilePath, (RingtoetsProject) project); } catch { CleanUpTemporaryFile(overwriteHelper, true); } CleanUpTemporaryFile(overwriteHelper, false); } } /// /// Converts to an existing in the database. /// /// Path to database file. /// to save. /// Thrown when is null. /// is invalid. /// No file is present at /// at the time a connection is made. /// Thrown when /// /// does not exist. /// The database does not contain the table version. /// Saving the to the database failed. /// The connection to the database file failed. /// The related entity was not found in the database. Therefore, no update was possible. /// /// public void SaveProject(string databaseFilePath, IProject project) { if (!(project is RingtoetsProject)) { throw new ArgumentNullException("project"); } try { SetConnectionToExistingFile(databaseFilePath); } catch { SaveProjectAs(databaseFilePath, project); } UpdateProjectInDatabase(databaseFilePath, (RingtoetsProject) project); } /// /// Attempts to load the from the SQLite database. /// /// Path to database file. /// Returns a new instance of with the data from the database or null when not found. /// is invalid. /// Thrown when /// /// does not exist. /// The database does not contain all requested tables. /// The connection to the database file failed. /// The related entity was not found in the database. /// /// public IProject LoadProject(string databaseFilePath) { SetConnectionToExistingFile(databaseFilePath); try { using (var dbContext = new RingtoetsEntities(connectionString)) { ProjectEntity projectEntity; try { projectEntity = dbContext.ProjectEntities.Single(); } catch (InvalidOperationException exception) { throw CreateStorageReaderException(databaseFilePath, Resources.StorageSqLite_LoadProject_Invalid_Ringtoets_database_file, exception); } var project = projectEntity.Read(new ReadConversionCollector()); project.Name = Path.GetFileNameWithoutExtension(databaseFilePath); return project; } } catch (DataException exception) { throw CreateStorageReaderException(databaseFilePath, Resources.StorageSqLite_LoadProject_Invalid_Ringtoets_database_file, exception); } catch (SystemException exception) { throw CreateStorageReaderException(databaseFilePath, Resources.StorageSqLite_LoadProject_Invalid_Ringtoets_database_file, exception); } } public void CloseProject() { connectionString = null; } public bool HasChanges(IProject project) { if (string.IsNullOrWhiteSpace(connectionString)) { return true; } if (!(project is RingtoetsProject)) { throw new ArgumentNullException("project"); } using (var dbContext = new RingtoetsEntities(connectionString)) { try { var ringtoetsProject = (RingtoetsProject) project; var persistenceRegistry = new PersistenceRegistry(); ringtoetsProject.Update(persistenceRegistry, dbContext); persistenceRegistry.RemoveUntouched(dbContext); return dbContext.ChangeTracker.HasChanges(); } catch (EntityNotFoundException) { return true; } catch (SystemException) { return true; } } } /// /// Cleans up a new . /// /// The to use for cleaning up. /// Value indicating whether the should revert to /// original file or keep the new file. /// A file IO operation fails while cleaning up using the /// . private void CleanUpTemporaryFile(SafeOverwriteFileHelper overwriteHelper, bool revert) { try { overwriteHelper.Finish(revert); } catch (IOException e) { throw new StorageException(e.Message, e); } } /// /// Creates a new . /// /// The path for which to create the . /// A new /// is invalid. /// A file IO operation fails while initializing the /// . private SafeOverwriteFileHelper GetSafeOverwriteFileHelper(string databaseFilePath) { try { return new SafeOverwriteFileHelper(databaseFilePath); } catch (IOException e) { throw new StorageException(e.Message, e); } } private void SaveProjectInDatabase(string databaseFilePath, RingtoetsProject project) { using (var dbContext = new RingtoetsEntities(connectionString)) { try { var registry = new PersistenceRegistry(); dbContext.ProjectEntities.Add(project.Create(registry)); dbContext.SaveChanges(); registry.TransferIds(); project.Name = Path.GetFileNameWithoutExtension(databaseFilePath); } catch (DataException exception) { throw CreateStorageWriterException(databaseFilePath, Resources.Error_Update_Database, exception); } catch (SystemException exception) { if (exception is InvalidOperationException || exception is NotSupportedException) { throw CreateStorageWriterException(databaseFilePath, Resources.Error_During_Connection, exception); } throw; } } } private void UpdateProjectInDatabase(string databaseFilePath, RingtoetsProject project) { using (var dbContext = new RingtoetsEntities(connectionString)) { try { var updateCollector = new PersistenceRegistry(); project.Update(updateCollector, dbContext); updateCollector.RemoveUntouched(dbContext); dbContext.SaveChanges(); updateCollector.TransferIds(); } catch (EntityNotFoundException e) { throw CreateStorageWriterException(databaseFilePath, e.Message, e); } catch (DataException exception) { throw CreateStorageWriterException(databaseFilePath, Resources.Error_Update_Database, exception); } catch (SystemException exception) { if (exception is InvalidOperationException || exception is NotSupportedException) { throw CreateStorageWriterException(databaseFilePath, Resources.Error_During_Connection, exception); } throw; } } } /// /// Attempts to set the connection to an existing storage file . /// /// Path to database file. /// is invalid. /// Thrown when: /// does not exist /// the database has an invalid schema. /// /// private void SetConnectionToExistingFile(string databaseFilePath) { FileUtils.ValidateFilePath(databaseFilePath); SetConnectionToFile(databaseFilePath); } /// /// Sets the connection to a newly created (empty) Ringtoets database file. /// /// Path to database file. /// is invalid. /// No file is present at /// at the time a connection is made. /// Thrown when: /// was not created /// executing DatabaseStructure script failed /// /// /// Thrown when the could not /// be overwritten. private void SetConnectionToNewFile(string databaseFilePath) { FileUtils.ValidateFilePath(databaseFilePath); StorageSqliteCreator.CreateDatabaseStructure(databaseFilePath); SetConnectionToFile(databaseFilePath); } /// /// Establishes a connection to an existing . /// /// The path of the database file to connect to. /// No file exists at . private void SetConnectionToFile(string databaseFilePath) { if (!File.Exists(databaseFilePath)) { var message = new FileReaderErrorMessageBuilder(databaseFilePath).Build(UtilsResources.Error_File_does_not_exist); throw new CouldNotConnectException(message); } SetConnectionToStorage(databaseFilePath); } /// /// Sets the connection to the Ringtoets database. /// /// The path of the file, which is used for creating exceptions. /// Thrown when the database does not contain the table version. private void SetConnectionToStorage(string databaseFilePath) { connectionString = SqLiteConnectionStringBuilder.BuildSqLiteEntityConnectionString(databaseFilePath); using (var dbContext = new RingtoetsEntities(connectionString)) { try { dbContext.Database.Initialize(true); dbContext.VersionEntities.Load(); } catch (Exception exception) { var message = new FileReaderErrorMessageBuilder(databaseFilePath).Build(string.Format(Resources.StorageSqLite_LoadProject_Invalid_Ringtoets_database_file, databaseFilePath)); throw new StorageValidationException(message, exception); } } } /// /// Throws a configured instance of when writing to the storage file failed. /// /// The path of the file that was attempted to connect with. /// The critical error message. /// Exception that caused this exception to be thrown. /// Returns a new . private StorageException CreateStorageWriterException(string databaseFilePath, string errorMessage, Exception innerException) { var message = new FileWriterErrorMessageBuilder(databaseFilePath).Build(errorMessage); return new StorageException(message, innerException); } /// /// Throws a configured instance of when reading the storage file failed. /// /// The path of the file that was attempted to connect with. /// The critical error message. /// Exception that caused this exception to be thrown. /// Returns a new . private StorageException CreateStorageReaderException(string databaseFilePath, string errorMessage, Exception innerException) { var message = new FileReaderErrorMessageBuilder(databaseFilePath).Build(errorMessage); return new StorageException(message, innerException); } } }