From 3146b82375c3a2c596a3c0addb5bfb99a85bf969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Th=C3=A9ate?= <97221392+antoineatrhea@users.noreply.github.com> Date: Tue, 5 Mar 2024 07:38:35 +0100 Subject: [PATCH] Fix #313: CometTask support (#315) * WIP First implementation at DAL level * Missing tests for CDP4ServicesDal * Fix CometTaskRoute * Unit tests using MockHttp * Fix SQ code smells * Session returns CometTask and not uses CDPMessageBus for CometTask --- CDP4-SDK.sln | 6 + CDP4Dal.NetCore.Tests/DAL/DalTestFixture.cs | 82 +++++ CDP4Dal.NetCore.Tests/SessionTestFixture.cs | 181 +++++++++ CDP4Dal.Tests/DAL/DalTestFixture.cs | 84 ++++- CDP4Dal.Tests/SessionTestFixture.cs | 179 +++++++++ CDP4Dal/CDP4Dal.csproj | 2 +- CDP4Dal/DAL/Dal.cs | 37 +- CDP4Dal/DAL/IDal.cs | 33 ++ .../ECSS1025AnnexC/QueryAttributes.cs | 11 + CDP4Dal/ISession.cs | 41 +++ CDP4Dal/Operations/LongRunningTaskResult.cs | 73 ++++ CDP4Dal/Session.cs | 200 ++++++++-- CDP4DalCommon/CDP4DalCommon.csproj | 41 +++ CDP4DalCommon/Tasks/CometTask.cs | 119 ++++++ CDP4DalCommon/Tasks/StatusKind.cs | 55 +++ .../JsonFileDalTestFixture.cs | 13 +- .../JsonFileDalTestFixture.cs | 12 +- CDP4JsonFileDal/JsonFileDal.cs | 76 +++- CDP4JsonSerializer/Cdp4JsonSerializer.cs | 2 + .../CDP4ServicesDal.NetCore.Tests.csproj | 1 + .../CdpServicesDalTestFixture.cs | 202 +++++++++++ .../CDP4ServicesDal.Tests.csproj | 1 + .../CdpServicesDalTestFixture.cs | 201 +++++++++- CDP4ServicesDal/CdpServicesDal.cs | 343 ++++++++++++++++-- CDP4WspDal.NetCore.Tests/WSPDalTestFixture.cs | 13 + CDP4WspDal.Tests/WSPDalTestFixture.cs | 13 + CDP4WspDal/WSPDal.cs | 44 ++- 27 files changed, 1976 insertions(+), 89 deletions(-) create mode 100644 CDP4Dal/Operations/LongRunningTaskResult.cs create mode 100644 CDP4DalCommon/CDP4DalCommon.csproj create mode 100644 CDP4DalCommon/Tasks/CometTask.cs create mode 100644 CDP4DalCommon/Tasks/StatusKind.cs diff --git a/CDP4-SDK.sln b/CDP4-SDK.sln index ff61c2c11..4b54bcb1e 100644 --- a/CDP4-SDK.sln +++ b/CDP4-SDK.sln @@ -77,6 +77,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CDP4ServicesMessaging", "CD EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CDP4ServicesMessaging.Tests", "CDP4ServicesMessaging.Tests\CDP4ServicesMessaging.Tests.csproj", "{210B96F7-2695-4750-8808-D895C8C2E303}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CDP4DalCommon", "CDP4DalCommon\CDP4DalCommon.csproj", "{E7CDB217-8442-4FD4-8E87-F4A8BFE9622A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -203,6 +205,10 @@ Global {210B96F7-2695-4750-8808-D895C8C2E303}.Debug|Any CPU.Build.0 = Debug|Any CPU {210B96F7-2695-4750-8808-D895C8C2E303}.Release|Any CPU.ActiveCfg = Release|Any CPU {210B96F7-2695-4750-8808-D895C8C2E303}.Release|Any CPU.Build.0 = Release|Any CPU + {E7CDB217-8442-4FD4-8E87-F4A8BFE9622A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E7CDB217-8442-4FD4-8E87-F4A8BFE9622A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E7CDB217-8442-4FD4-8E87-F4A8BFE9622A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E7CDB217-8442-4FD4-8E87-F4A8BFE9622A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/CDP4Dal.NetCore.Tests/DAL/DalTestFixture.cs b/CDP4Dal.NetCore.Tests/DAL/DalTestFixture.cs index a792d859c..769ec3409 100644 --- a/CDP4Dal.NetCore.Tests/DAL/DalTestFixture.cs +++ b/CDP4Dal.NetCore.Tests/DAL/DalTestFixture.cs @@ -40,6 +40,8 @@ namespace CDP4Dal.Tests.DAL using CDP4Dal.Operations; using CDP4Dal.DAL; + using CDP4DalCommon.Tasks; + using NUnit.Framework; using Thing = CDP4Common.DTO.Thing; @@ -339,6 +341,25 @@ public override Task> Write(OperationContainer operationConta throw new System.NotImplementedException(); } + /// + /// Write all the s from an asynchronously for a possible long running task. + /// + /// + /// The provided to write + /// + /// The maximum time that we allow the server before responding. If the write operation takes more time + /// than the provided , a + /// + /// The path to the files that need to be uploaded. If is null, then no files are to be uploaded + /// + /// + /// A list of s that has been created or updated since the last Read or Write operation. + /// + public override Task Write(OperationContainer operationContainer, int waitTime, IEnumerable files = null) + { + throw new System.NotImplementedException(); + } + public override Task> Read(T thing, CancellationToken token, IQueryAttributes attributes = null) { throw new System.NotImplementedException(); @@ -354,6 +375,27 @@ public override Task> Read(IEnumerable + /// Reads the identified by the provided + /// + /// The identifier + /// The + /// The read + public override Task ReadCometTask(Guid id, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + /// Reads all available for the current logged + /// + /// The + /// All available + public override Task> ReadCometTasks(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + public override Task ReadFile(Thing localFile, CancellationToken cancellationToken) { throw new NotImplementedException(); @@ -419,6 +461,25 @@ public override Task> Write(OperationContainer operationConta throw new NotImplementedException(); } + /// + /// Write all the s from an asynchronously for a possible long running task. + /// + /// + /// The provided to write + /// + /// The maximum time that we allow the server before responding. If the write operation takes more time + /// than the provided , a + /// + /// The path to the files that need to be uploaded. If is null, then no files are to be uploaded + /// + /// + /// A list of s that has been created or updated since the last Read or Write operation. + /// + public override Task Write(OperationContainer operationContainer, int waitTime, IEnumerable files = null) + { + throw new System.NotImplementedException(); + } + public override Task> Read(T thing, CancellationToken token, IQueryAttributes attributes = null) { throw new NotImplementedException(); @@ -434,6 +495,27 @@ public override Task> Read(IEnumerable + /// Reads the identified by the provided + /// + /// The identifier + /// The + /// The read + public override Task ReadCometTask(Guid id, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + /// Reads all available for the current logged + /// + /// The + /// All available + public override Task> ReadCometTasks(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + public override Task ReadFile(Thing localFile, CancellationToken cancellationToken) { throw new NotImplementedException(); diff --git a/CDP4Dal.NetCore.Tests/SessionTestFixture.cs b/CDP4Dal.NetCore.Tests/SessionTestFixture.cs index 6a6d19d21..d8e13ce03 100644 --- a/CDP4Dal.NetCore.Tests/SessionTestFixture.cs +++ b/CDP4Dal.NetCore.Tests/SessionTestFixture.cs @@ -40,6 +40,9 @@ namespace CDP4Dal.NetCore.Tests using CDP4Dal.Operations; using CDP4Dal.DAL; using CDP4Dal.Events; + using CDP4Dal.Exceptions; + + using CDP4DalCommon.Tasks; using Moq; @@ -48,9 +51,11 @@ namespace CDP4Dal.NetCore.Tests using DomainOfExpertise = CDP4Common.SiteDirectoryData.DomainOfExpertise; using EngineeringModelSetup = CDP4Common.DTO.EngineeringModelSetup; using ModelReferenceDataLibrary = CDP4Common.SiteDirectoryData.ModelReferenceDataLibrary; + using Person = CDP4Common.SiteDirectoryData.Person; using SiteDirectory = CDP4Common.DTO.SiteDirectory; using SiteReferenceDataLibrary = CDP4Common.SiteDirectoryData.SiteReferenceDataLibrary; using TelephoneNumber = CDP4Common.SiteDirectoryData.TelephoneNumber; + using TextParameterType = CDP4Common.DTO.TextParameterType; using Thing = CDP4Common.DTO.Thing; /// @@ -685,12 +690,148 @@ public void VerifyThatCancelWriteWorks() this.mockedDal.Verify(x => x.Write(It.IsAny(), It.IsAny>()), Times.Exactly(0)); } + + [Test] + public async Task VerifyCanReadCometTask() + { + var cometTaskId = Guid.NewGuid(); + + Assert.Multiple(() => + { + Assert.That(this.session.ActivePerson, Is.Null); + Assert.That(() => this.session.ReadCometTask(cometTaskId), Throws.InvalidOperationException); + }); + + this.AssignActivePerson(); + + var returnedCometTask = new CometTask() + { + Id = cometTaskId + }; + + this.mockedDal.Setup(x => x.ReadCometTask(cometTaskId, It.IsAny())).ReturnsAsync(returnedCometTask); + + var cometTask = await this.session.ReadCometTask(cometTaskId); + + Assert.Multiple(() => + { + Assert.That(cometTask, Is.Not.Null); + Assert.That(cometTask, Is.EqualTo(returnedCometTask)); + Assert.That(this.session.CometTasks[cometTaskId], Is.EqualTo(returnedCometTask)); + }); + + this.mockedDal.Setup(x => x.ReadCometTask(cometTaskId, It.IsAny())).Throws(); + cometTask = await this.session.ReadCometTask(cometTaskId); + + Assert.Multiple(() => + { + Assert.That(cometTask, Is.EqualTo((CometTask)default)); + Assert.That(this.session.CometTasks[cometTaskId], Is.EqualTo(returnedCometTask)); + }); + + this.mockedDal.Setup(x => x.ReadCometTask(cometTaskId, It.IsAny())).Throws(); + Assert.That(() => this.session.ReadCometTask(cometTaskId), Throws.Exception.TypeOf()); + } + + [Test] + public async Task VerifyCanReadCometTasks() + { + Assert.Multiple(() => + { + Assert.That(this.session.ActivePerson, Is.Null); + Assert.That(() => this.session.ReadCometTasks(), Throws.InvalidOperationException); + }); + + this.AssignActivePerson(); + + var returnedCometTasks = new List() + { + new () + { + Id = Guid.NewGuid() + }, + new () + { + Id = Guid.NewGuid() + }, + }; + + this.mockedDal.Setup(x => x.ReadCometTasks(It.IsAny())).ReturnsAsync(returnedCometTasks); + + var cometTasks = await this.session.ReadCometTasks(); + + Assert.Multiple(() => + { + Assert.That(cometTasks, Is.Not.Empty); + Assert.That(cometTasks, Is.EquivalentTo(returnedCometTasks)); + Assert.That(this.session.CometTasks, Has.Count.EqualTo(returnedCometTasks.Count)); + }); + + this.mockedDal.Setup(x => x.ReadCometTasks(It.IsAny())).Throws(); + cometTasks = await this.session.ReadCometTasks(); + + Assert.Multiple(() => + { + Assert.That(cometTasks, Is.Empty); + Assert.That(this.session.CometTasks, Has.Count.EqualTo(returnedCometTasks.Count)); + }); + + this.mockedDal.Setup(x => x.ReadCometTasks(It.IsAny())).Throws(); + Assert.That(() => this.session.ReadCometTasks(), Throws.Exception.TypeOf()); + } + + [Test] + public async Task VerifyWritePossibleLongRunningTask() + { + var context = $"/SiteDirectory/{Guid.NewGuid()}"; + this.AssignActivePerson(); + + this.mockedDal.Setup(x => x.Write(It.IsAny(), It.IsAny(), It.IsAny>())) + .ReturnsAsync(new LongRunningTaskResult(new CometTask() { Id = Guid.Empty })); + + var cometTask = await this.session.Write(new OperationContainer(context), 1); + + Assert.Multiple(() => + { + Assert.That(cometTask.HasValue, Is.True); + Assert.That(this.session.CometTasks, Is.Not.Empty); + }); + + this.mockedDal.Setup(x => x.Write(It.IsAny(), It.IsAny(), It.IsAny>())) + .ReturnsAsync(new LongRunningTaskResult(new List() + { + new TextParameterType() + { + Iid = Guid.NewGuid() + } + })); + + cometTask = await this.session.Write(new OperationContainer(context), 1); + + Assert.Multiple(() => + { + Assert.That(cometTask.HasValue, Is.False); + Assert.That(this.session.CometTasks, Is.Not.Empty); + }); + + this.mockedDal.Setup(x => x.Write(It.IsAny(), It.IsAny(), It.IsAny>())) + .ThrowsAsync(new DalReadException()); + + Assert.That(() =>this.session.Write(new OperationContainer(context), 1), Throws.Exception.TypeOf()); + } + + private void AssignActivePerson() + { + var johnDoe = new Person(this.person.Iid, this.session.Assembler.Cache, this.uri) { ShortName = "John" }; + this.session.GetType().GetProperty("ActivePerson")?.SetValue(this.session, johnDoe, null); + } } [DalExport("test dal", "test dal description", "1.1.0", DalType.Web)] internal class TestDal : IDal { public Version SupportedVersion { get {return new Version(1, 0, 0);} } + public Version DalVersion { get {return new Version("1.1.0");} } public IMetaDataProvider MetaDataProvider { get {return new MetaDataProvider();} } @@ -735,6 +876,25 @@ public Task> Write(OperationContainer operationContainer, IEn throw new NotImplementedException(); } + /// + /// Write all the s from an asynchronously for a possible long running task. + /// + /// + /// The provided to write + /// + /// The maximum time that we allow the server before responding. If the write operation takes more time + /// than the provided , a + /// + /// The path to the files that need to be uploaded. If is null, then no files are to be uploaded + /// + /// + /// A list of s that has been created or updated since the last Read or Write operation. + /// + public Task Write(OperationContainer operationContainer, int waitTime, IEnumerable files = null) + { + throw new System.NotImplementedException(); + } + /// /// Reads the data related to the provided from the data-source /// @@ -805,6 +965,27 @@ public Task> Read(IEnumerable en throw new NotImplementedException(); } + /// + /// Reads the identified by the provided + /// + /// The identifier + /// The + /// The read + public Task ReadCometTask(Guid id, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + /// Reads all available for the current logged + /// + /// The + /// All available + public Task> ReadCometTasks(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + public Task ReadFile(Thing localFile, CancellationToken cancellationToken) { throw new NotImplementedException(); diff --git a/CDP4Dal.Tests/DAL/DalTestFixture.cs b/CDP4Dal.Tests/DAL/DalTestFixture.cs index f431d88a4..beb304103 100644 --- a/CDP4Dal.Tests/DAL/DalTestFixture.cs +++ b/CDP4Dal.Tests/DAL/DalTestFixture.cs @@ -40,6 +40,8 @@ namespace CDP4Dal.Tests.DAL using CDP4Dal.Operations; using CDP4Dal.DAL; + using CDP4DalCommon.Tasks; + using NUnit.Framework; using Thing = CDP4Common.DTO.Thing; @@ -302,7 +304,7 @@ public void Verify_that_OperationContainerFileVerification_throws_no_exception_w internal class TestDal : Dal { public override bool IsReadOnly { get { return false; } } - + public TestDal(Credentials credentials) : base() { @@ -339,6 +341,25 @@ public override Task> Write(OperationContainer operationConta throw new System.NotImplementedException(); } + /// + /// Write all the s from an asynchronously for a possible long running task. + /// + /// + /// The provided to write + /// + /// The maximum time that we allow the server before responding. If the write operation takes more time + /// than the provided , a + /// + /// The path to the files that need to be uploaded. If is null, then no files are to be uploaded + /// + /// + /// A list of s that has been created or updated since the last Read or Write operation. + /// + public override Task Write(OperationContainer operationContainer, int waitTime, IEnumerable files = null) + { + return null; + } + public override Task> Read(T thing, CancellationToken token, IQueryAttributes attributes = null) { throw new System.NotImplementedException(); @@ -354,6 +375,27 @@ public override Task> Read(IEnumerable + /// Reads the identified by the provided + /// + /// The identifier + /// The + /// The read + public override Task ReadCometTask(Guid id, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + /// Reads all available for the current logged + /// + /// The + /// All available + public override Task> ReadCometTasks(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + public override Task ReadFile(Thing thing, CancellationToken cancellationToken) { throw new NotImplementedException(); @@ -419,6 +461,25 @@ public override Task> Write(OperationContainer operationConta throw new NotImplementedException(); } + /// + /// Write all the s from an asynchronously for a possible long running task. + /// + /// + /// The provided to write + /// + /// The maximum time that we allow the server before responding. If the write operation takes more time + /// than the provided , a + /// + /// The path to the files that need to be uploaded. If is null, then no files are to be uploaded + /// + /// + /// A list of s that has been created or updated since the last Read or Write operation. + /// + public override Task Write(OperationContainer operationContainer, int waitTime, IEnumerable files = null) + { + return null; + } + public override Task> Read(T thing, CancellationToken token, IQueryAttributes attributes = null) { throw new NotImplementedException(); @@ -434,6 +495,27 @@ public override Task> Read(IEnumerable + /// Reads the identified by the provided + /// + /// The identifier + /// The + /// The read + public override Task ReadCometTask(Guid id, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + /// Reads all available for the current logged + /// + /// The + /// All available + public override Task> ReadCometTasks(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + public override Task ReadFile(Thing thing, CancellationToken cancellationToken) { throw new NotImplementedException(); diff --git a/CDP4Dal.Tests/SessionTestFixture.cs b/CDP4Dal.Tests/SessionTestFixture.cs index 4fd5a10b8..6b6b45adf 100644 --- a/CDP4Dal.Tests/SessionTestFixture.cs +++ b/CDP4Dal.Tests/SessionTestFixture.cs @@ -40,6 +40,9 @@ namespace CDP4Dal.Tests using CDP4Dal.Operations; using CDP4Dal.DAL; using CDP4Dal.Events; + using CDP4Dal.Exceptions; + + using CDP4DalCommon.Tasks; using Moq; @@ -48,9 +51,11 @@ namespace CDP4Dal.Tests using DomainOfExpertise = CDP4Common.SiteDirectoryData.DomainOfExpertise; using EngineeringModelSetup = CDP4Common.DTO.EngineeringModelSetup; using ModelReferenceDataLibrary = CDP4Common.SiteDirectoryData.ModelReferenceDataLibrary; + using Person = CDP4Common.SiteDirectoryData.Person; using SiteDirectory = CDP4Common.DTO.SiteDirectory; using SiteReferenceDataLibrary = CDP4Common.SiteDirectoryData.SiteReferenceDataLibrary; using TelephoneNumber = CDP4Common.SiteDirectoryData.TelephoneNumber; + using TextParameterType = CDP4Common.DTO.TextParameterType; using Thing = CDP4Common.DTO.Thing; /// @@ -790,6 +795,140 @@ public async Task VerifyCanCherryPick() Assert.That(readThings, Has.Count.EqualTo(2)); }); } + + [Test] + public async Task VerifyCanReadCometTask() + { + var cometTaskId = Guid.NewGuid(); + + Assert.Multiple(() => + { + Assert.That(this.session.ActivePerson, Is.Null); + Assert.That(() => this.session.ReadCometTask(cometTaskId), Throws.InvalidOperationException); + }); + + this.AssignActivePerson(); + + var returnedCometTask = new CometTask() + { + Id = cometTaskId + }; + + this.mockedDal.Setup(x => x.ReadCometTask(cometTaskId, It.IsAny())).ReturnsAsync(returnedCometTask); + + var cometTask = await this.session.ReadCometTask(cometTaskId); + + Assert.Multiple(() => + { + Assert.That(cometTask, Is.Not.Null); + Assert.That(cometTask, Is.EqualTo(returnedCometTask)); + Assert.That(this.session.CometTasks[cometTaskId], Is.EqualTo(returnedCometTask)); + }); + + this.mockedDal.Setup(x => x.ReadCometTask(cometTaskId, It.IsAny())).Throws(); + cometTask = await this.session.ReadCometTask(cometTaskId); + + Assert.Multiple(() => + { + Assert.That(cometTask, Is.EqualTo((CometTask)default)); + Assert.That(this.session.CometTasks[cometTaskId], Is.EqualTo(returnedCometTask)); + }); + + this.mockedDal.Setup(x => x.ReadCometTask(cometTaskId, It.IsAny())).Throws(); + Assert.That(() => this.session.ReadCometTask(cometTaskId), Throws.Exception.TypeOf()); + } + + [Test] + public async Task VerifyCanReadCometTasks() + { + Assert.Multiple(() => + { + Assert.That(this.session.ActivePerson, Is.Null); + Assert.That(() => this.session.ReadCometTasks(), Throws.InvalidOperationException); + }); + + this.AssignActivePerson(); + + var returnedCometTasks = new List() + { + new CometTask() + { + Id = Guid.NewGuid() + }, + new CometTask() + { + Id = Guid.NewGuid() + }, + }; + + this.mockedDal.Setup(x => x.ReadCometTasks(It.IsAny())).ReturnsAsync(returnedCometTasks); + + var cometTasks = await this.session.ReadCometTasks(); + + Assert.Multiple(() => + { + Assert.That(cometTasks, Is.Not.Empty); + Assert.That(cometTasks, Is.EquivalentTo(returnedCometTasks)); + Assert.That(this.session.CometTasks, Has.Count.EqualTo(returnedCometTasks.Count)); + }); + + this.mockedDal.Setup(x => x.ReadCometTasks(It.IsAny())).Throws(); + cometTasks = await this.session.ReadCometTasks(); + + Assert.Multiple(() => + { + Assert.That(cometTasks, Is.Empty); + Assert.That(this.session.CometTasks, Has.Count.EqualTo(returnedCometTasks.Count)); + }); + + this.mockedDal.Setup(x => x.ReadCometTasks(It.IsAny())).Throws(); + Assert.That(() => this.session.ReadCometTasks(), Throws.Exception.TypeOf()); + } + + [Test] + public async Task VerifyWritePossibleLongRunningTask() + { + var context = $"/SiteDirectory/{Guid.NewGuid()}"; + this.AssignActivePerson(); + + this.mockedDal.Setup(x => x.Write(It.IsAny(), It.IsAny(), It.IsAny>())) + .ReturnsAsync(new LongRunningTaskResult(new CometTask() { Id = Guid.Empty })); + + var cometTask = await this.session.Write(new OperationContainer(context), 1); + + Assert.Multiple(() => + { + Assert.That(cometTask.HasValue, Is.True); + Assert.That(this.session.CometTasks, Is.Not.Empty); + }); + + this.mockedDal.Setup(x => x.Write(It.IsAny(), It.IsAny(), It.IsAny>())) + .ReturnsAsync(new LongRunningTaskResult(new List() + { + new TextParameterType() + { + Iid = Guid.NewGuid() + } + })); + + cometTask = await this.session.Write(new OperationContainer(context), 1); + + Assert.Multiple(() => + { + Assert.That(cometTask.HasValue, Is.False); + Assert.That(this.session.CometTasks, Is.Not.Empty); + }); + + this.mockedDal.Setup(x => x.Write(It.IsAny(), It.IsAny(), It.IsAny>())) + .ThrowsAsync(new DalReadException()); + + Assert.That(() =>this.session.Write(new OperationContainer(context), 1), Throws.Exception.TypeOf()); + } + private void AssignActivePerson() + { + var johnDoe = new Person(this.person.Iid, this.session.Assembler.Cache, this.uri) { ShortName = "John" }; + this.session.GetType().GetProperty("ActivePerson")?.SetValue(this.session, johnDoe, null); + } } [DalExport("test dal", "test dal description", "1.1.0", DalType.Web)] @@ -840,6 +979,25 @@ public Task> Write(OperationContainer operationContainer, IEn throw new NotImplementedException(); } + /// + /// Write all the s from an asynchronously for a possible long running task. + /// + /// + /// The provided to write + /// + /// The maximum time that we allow the server before responding. If the write operation takes more time + /// than the provided , a + /// + /// The path to the files that need to be uploaded. If is null, then no files are to be uploaded + /// + /// + /// A list of s that has been created or updated since the last Read or Write operation. + /// + public Task Write(OperationContainer operationContainer, int waitTime, IEnumerable files = null) + { + return null; + } + /// /// Reads the data related to the provided from the data-source /// @@ -894,6 +1052,27 @@ public Task> Read(IEnumerable en throw new NotImplementedException(); } + /// + /// Reads the identified by the provided + /// + /// The identifier + /// The + /// The read + public Task ReadCometTask(Guid id, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + /// Reads all available for the current logged + /// + /// The + /// All available + public Task> ReadCometTasks(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + public Task ReadFile(Thing localFile, CancellationToken cancellationToken) { throw new NotImplementedException(); diff --git a/CDP4Dal/CDP4Dal.csproj b/CDP4Dal/CDP4Dal.csproj index 0780b5a53..7e76283c4 100644 --- a/CDP4Dal/CDP4Dal.csproj +++ b/CDP4Dal/CDP4Dal.csproj @@ -40,7 +40,7 @@ - + diff --git a/CDP4Dal/DAL/Dal.cs b/CDP4Dal/DAL/Dal.cs index 7a913a6cd..578de2fb5 100644 --- a/CDP4Dal/DAL/Dal.cs +++ b/CDP4Dal/DAL/Dal.cs @@ -40,7 +40,9 @@ namespace CDP4Dal.DAL using CDP4Dal.Operations; using CDP4Dal.Composition; using CDP4Dal.Exceptions; - + + using CDP4DalCommon.Tasks; + using NLog; using Iteration = CDP4Common.DTO.Iteration; @@ -118,6 +120,22 @@ protected Dal() /// public abstract Task> Write(OperationContainer operationContainer, IEnumerable files = null); + /// + /// Write all the s from an asynchronously for a possible long running task. + /// + /// + /// The provided to write + /// + /// The maximum time that we allow the server before responding. If the write operation takes more time + /// than the provided , a + /// + /// The path to the files that need to be uploaded. If is null, then no files are to be uploaded + /// + /// + /// A list of s that has been created or updated since the last Read or Write operation. + /// + public abstract Task Write(OperationContainer operationContainer, int waitTime, IEnumerable files = null); + /// /// Reads the data related to the provided from the data-source /// @@ -173,7 +191,22 @@ protected Dal() /// Only those s are retunred that the is a in /// public abstract Task> Read(IEnumerable engineeringModels, CancellationToken cancellationToken); - + + /// + /// Reads the identified by the provided + /// + /// The identifier + /// The + /// The read + public abstract Task ReadCometTask(Guid id, CancellationToken cancellationToken); + + /// + /// Reads all available for the current logged + /// + /// The + /// All available + public abstract Task> ReadCometTasks(CancellationToken cancellationToken); + /// /// Reads a physical file from a DataStore /// diff --git a/CDP4Dal/DAL/IDal.cs b/CDP4Dal/DAL/IDal.cs index e28e8129f..368491b71 100644 --- a/CDP4Dal/DAL/IDal.cs +++ b/CDP4Dal/DAL/IDal.cs @@ -35,6 +35,8 @@ namespace CDP4Dal.DAL using CDP4Dal.Operations; + using CDP4DalCommon.Tasks; + using Thing = CDP4Common.DTO.Thing; /// @@ -89,6 +91,22 @@ public interface IDal /// A list of s that has been created or updated since the last Read or Write operation. /// Task> Write(OperationContainer operationContainer, IEnumerable files = null); + + /// + /// Write all the s from an asynchronously for a possible long running task. + /// + /// + /// The provided to write + /// + /// The maximum time that we allow the server before responding. If the write operation takes more time + /// than the provided , a + /// + /// The path to the files that need to be uploaded. If is null, then no files are to be uploaded + /// + /// + /// A list of s that has been created or updated since the last Read or Write operation. + /// + Task Write(OperationContainer operationContainer, int waitTime, IEnumerable files = null); /// /// Reads the data related to the provided from the data-source @@ -148,6 +166,21 @@ public interface IDal /// Task> Read(IEnumerable engineeringModels, CancellationToken cancellationToken); + /// + /// Reads the identified by the provided + /// + /// The identifier + /// The + /// The read + Task ReadCometTask(Guid id, CancellationToken cancellationToken); + + /// + /// Reads all available for the current logged + /// + /// The + /// All available + Task> ReadCometTasks(CancellationToken cancellationToken); + /// /// Reads a physical file from a DataStore /// diff --git a/CDP4Dal/DAL/Protocol/ECSS1025AnnexC/QueryAttributes.cs b/CDP4Dal/DAL/Protocol/ECSS1025AnnexC/QueryAttributes.cs index 13346ad66..7f6f20e59 100644 --- a/CDP4Dal/DAL/Protocol/ECSS1025AnnexC/QueryAttributes.cs +++ b/CDP4Dal/DAL/Protocol/ECSS1025AnnexC/QueryAttributes.cs @@ -72,6 +72,11 @@ public class QueryAttributes : DalQueryAttributes /// public bool? CherryPick { get; set; } + /// + /// Gets or sets the amount of time a user waits before a CometTask is returned instead of the 10-25 Thing response + /// + public int WaitTime { get; set; } + /// /// Converts all values of this class to a uri attributes string /// @@ -129,8 +134,14 @@ public override string JoinAttributes() attributeList.Add($"cherryPick={this.CherryPick.ToString().ToLower()}"); } + if (this.WaitTime > 0) + { + attributeList.Add($"waitTime={this.WaitTime}"); + } + // include the base attributelist var baseJoinedAttributes = base.JoinAttributes(); + if (!string.IsNullOrEmpty(baseJoinedAttributes)) { attributeList.Add(baseJoinedAttributes); diff --git a/CDP4Dal/ISession.cs b/CDP4Dal/ISession.cs index 68ac78a83..75213100e 100644 --- a/CDP4Dal/ISession.cs +++ b/CDP4Dal/ISession.cs @@ -37,6 +37,8 @@ namespace CDP4Dal using CDP4Dal.DAL; using CDP4Dal.Events; + using CDP4DalCommon.Tasks; + using Permission; /// @@ -121,6 +123,11 @@ public interface ISession /// ICDPMessageBus CDPMessageBus { get; } + /// + /// Gets the of available + /// + IReadOnlyDictionary CometTasks { get; } + /// /// Retrieves the in the context of the current session /// @@ -263,6 +270,21 @@ public interface ISession /// Task Read(IEnumerable engineeringModels); + /// + /// Reads a identified by the provided + /// + /// The identifier for the + /// An await-able with the read + /// If the is null, meaning that the session is not opened + Task ReadCometTask(Guid id); + + /// + /// Reads all available for the current logged + /// + /// An await-able with the of read + /// If the is null, meaning that the session is not opened + Task> ReadCometTasks(); + /// /// Reads a physical file from a DataStore /// @@ -293,6 +315,25 @@ public interface ISession /// Task Write(OperationContainer operationContainer); + /// + /// Write all the s from an asynchronously for a possible long running task. + /// + /// + /// The provided to write + /// + /// The maximum time that we allow the server before responding. If the write operation takes more time + /// than the provided , a + /// + /// The path to the files that need to be uploaded. If is null, then no files are to be uploaded + /// + /// + /// An await-able with nullable . If the write operation took less time + /// than the provided , null is returned. + /// If the write operation takes longer than the provided , the associated + /// is returned. + /// + Task Write(OperationContainer operationContainer, int waitTime, IEnumerable files = null); + /// /// Refreshes all the s in the cache /// diff --git a/CDP4Dal/Operations/LongRunningTaskResult.cs b/CDP4Dal/Operations/LongRunningTaskResult.cs new file mode 100644 index 000000000..b493cb5c4 --- /dev/null +++ b/CDP4Dal/Operations/LongRunningTaskResult.cs @@ -0,0 +1,73 @@ +// ------------------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2015-2024 RHEA System S.A. +// +// Author: Sam Gerené, Alex Vorobiev, Alexander van Delft, Nathanael Smiechowski, Antoine Théate, Omar Elebiary, Jaime Bernar +// +// This file is part of CDP4-COMET SDK Community Edition +// +// The CDP4-COMET SDK Community Edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 3 of the License, or (at your option) any later version. +// +// The CDP4-COMET SDK Community Edition 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// ------------------------------------------------------------------------------------------------------------------------------- + +namespace CDP4Dal.Operations +{ + using System.Collections.Generic; + + using CDP4Common.DTO; + + using CDP4DalCommon.Tasks; + + /// + /// Handle the returned data of a possible long running task + /// + public class LongRunningTaskResult + { + /// + /// Initializes a new instance of the class. + /// + /// The collection of + public LongRunningTaskResult(IEnumerable things) + { + this.Things = things; + } + + /// + /// Initializes a new instance of the class. + /// + /// The + public LongRunningTaskResult(CometTask task) + { + this.Task = task; + } + + /// + /// Gets the collection of that can be retrieved from the response of a long running task if + /// the task finished before the wait time is reached + /// + public IEnumerable Things { get; } + + /// + /// Gets the possible that can be retrieved from the response of a long running task if + /// the task is not finished before the wait time is reached + /// + public CometTask Task { get; } + + /// + /// Asserts that lon running task takes more time that the defined wait time and that the wait time has been reached + /// + public bool IsWaitTimeReached => this.Things == null; + } +} diff --git a/CDP4Dal/Session.cs b/CDP4Dal/Session.cs index 2e39dc3f1..302bb5b17 100644 --- a/CDP4Dal/Session.cs +++ b/CDP4Dal/Session.cs @@ -47,6 +47,8 @@ namespace CDP4Dal using CDP4Dal.Operations; using CDP4Dal.Permission; + using CDP4DalCommon.Tasks; + using NLog; /// @@ -80,6 +82,11 @@ public class Session : ISession /// private readonly Dictionary> openIterations; + /// + /// Contains all created or read during the session + /// + private readonly Dictionary cometTasks = new Dictionary(); + /// /// Initializes a new instance of the class. /// @@ -125,6 +132,11 @@ public Session(IDal dal, Credentials credentials, ICDPMessageBus messageBus) /// public Version DalVersion => this.Dal.DalVersion; + /// + /// Gets the of available + /// + public IReadOnlyDictionary CometTasks => this.cometTasks; + /// /// Asserts whether the is supported by the connected /// @@ -318,6 +330,7 @@ public async Task Open(bool activeMessageBus = true) var sw = new Stopwatch(); sw.Start(); logger.Info("Open request {0}", this.DataSourceUri); + this.cometTasks.Clear(); // Create the token source var cancellationTokenSource = new CancellationTokenSource(); @@ -663,6 +676,86 @@ public async Task Read(IEnumerable things, IQueryAttributes queryAttribut await this.AfterReadOrWriteOrUpdate(foundThings); } + /// + /// Reads a identified by the provided + /// + /// The identifier for the + /// An await-able with the read + /// If the is null, meaning that the session is not opened + public async Task ReadCometTask(Guid id) + { + if (this.ActivePerson == null) + { + throw new InvalidOperationException("The CometTask cannot be read when the ActivePerson is null; The Open method must be called prior to any of the Read methods"); + } + + // Create the token source + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationTokenKey = Guid.NewGuid(); + this.cancellationTokenSourceDictionary.TryAdd(cancellationTokenKey, cancellationTokenSource); + CometTask cometTask = default; + + try + { + this.Dal.Session = this; + cometTask = await this.Dal.ReadCometTask(id, cancellationTokenSource.Token); + this.cometTasks[cometTask.Id] = cometTask; + cancellationTokenSource.Token.ThrowIfCancellationRequested(); + } + catch (OperationCanceledException) + { + logger.Info("Session.Read for CometTask {0} cancelled", id); + } + finally + { + this.cancellationTokenSourceDictionary.TryRemove(cancellationTokenKey, out _); + } + + return cometTask; + } + + /// + /// Reads all available for the current logged + /// + /// An await-able with the of read + /// If the is null, meaning that the session is not opened + public async Task> ReadCometTasks() + { + if (this.ActivePerson == null) + { + throw new InvalidOperationException("CometTasks cannot be read when the ActivePerson is null; The Open method must be called prior to any of the Read methods"); + } + + // Create the token source + var cancellationTokenSource = new CancellationTokenSource(); + var cancellationTokenKey = Guid.NewGuid(); + this.cancellationTokenSourceDictionary.TryAdd(cancellationTokenKey, cancellationTokenSource); + var readCometTasks = new List(); + + try + { + this.Dal.Session = this; + readCometTasks.AddRange(await this.Dal.ReadCometTasks(cancellationTokenSource.Token)); + + foreach (var cometTask in readCometTasks) + { + this.cometTasks[cometTask.Id] = cometTask; + } + + cancellationTokenSource.Token.ThrowIfCancellationRequested(); + } + catch (OperationCanceledException) + { + logger.Info("Session.Read for all CometTask cancelled"); + } + finally + { + this.cancellationTokenSourceDictionary.TryRemove(cancellationTokenKey, out _); + } + + return readCometTasks; + } + /// /// Reads a physical file from a DataStore /// @@ -738,33 +831,7 @@ private async Task AfterReadOrWriteOrUpdate(IList things, /// public async Task Write(OperationContainer operationContainer, IEnumerable files) { - if (this.ActivePerson == null) - { - throw new InvalidOperationException("The Write operation cannot be performed when the ActivePerson is null; The Open method must be called prior to performing a Write."); - } - - var filesList = files?.ToList(); - - if (filesList != null && filesList.Any()) - { - foreach (var file in filesList) - { - if (!System.IO.File.Exists(file)) - { - throw new FileNotFoundException($"File {file} was not found."); - } - } - } - - var eventArgs = new BeforeWriteEventArgs(operationContainer, filesList); - this.BeforeWrite?.Invoke(this, eventArgs); - - if (eventArgs.Cancelled) - { - throw new OperationCanceledException("The Write operation was canceled."); - } - - this.Dal.Session = this; + var filesList = this.BeforeDalWriteAndProcessFiles(operationContainer, files); var dtoThings = await this.Dal.Write(operationContainer, filesList); var enumerable = dtoThings as IList ?? dtoThings.ToList(); @@ -781,9 +848,42 @@ public async Task Write(OperationContainer operationContainer, IEnumerable /// an await-able /// - public async Task Write(OperationContainer operationContainer) + public Task Write(OperationContainer operationContainer) { - await this.Write(operationContainer, null); + return this.Write(operationContainer, null); + } + + /// + /// Write all the s from an asynchronously for a possible long running task. + /// + /// + /// The provided to write + /// + /// The maximum time that we allow the server before responding. If the write operation takes more time + /// than the provided , a + /// + /// The path to the files that need to be uploaded. If is null, then no files are to be uploaded + /// + /// + /// An await-able with nullable . If the write operation took less time + /// than the provided , null is returned. + /// If the write operation takes longer than the provided , the associated + /// is returned. + /// + public async Task Write(OperationContainer operationContainer, int waitTime, IEnumerable files = null) + { + var filesList = this.BeforeDalWriteAndProcessFiles(operationContainer, files); + var longRunningTaskResult = await this.Dal.Write(operationContainer, waitTime, filesList); + + if (longRunningTaskResult.IsWaitTimeReached) + { + this.cometTasks[longRunningTaskResult.Task.Id] = longRunningTaskResult.Task; + return longRunningTaskResult.Task; + } + + var things = longRunningTaskResult.Things as IList ?? longRunningTaskResult.Things.ToList(); + await this.AfterReadOrWriteOrUpdate(things); + return null; } /// @@ -888,6 +988,7 @@ public void Cancel() /// public async Task Close() { + this.cometTasks.Clear(); this.Dal.Close(); await this.Assembler.Clear(); @@ -1157,5 +1258,46 @@ private void AddIterationToOpenList(Guid iterationId, DomainOfExpertise activeDo var modelRdl = ((EngineeringModel) iteration.Container).EngineeringModelSetup.RequiredRdl.Single(); this.AddRdlToOpenList(modelRdl); } + + /// + /// Verifies that a write operation can be performed an process the provided + /// + /// + /// List of file paths for files to be sent to the datastore + /// The provided if not null, an empty collection either + /// If the is null, meaning that the session is not opened + /// If one of the provided filepath inside the does not exists + /// If the write operation has been canceled + private IEnumerable BeforeDalWriteAndProcessFiles(OperationContainer operationContainer, IEnumerable files = null) + { + if (this.ActivePerson == null) + { + throw new InvalidOperationException("The Write operation cannot be performed when the ActivePerson is null; The Open method must be called prior to performing a Write."); + } + + var filesList = files?.ToList(); + + if (filesList != null && filesList.Any()) + { + foreach (var file in filesList) + { + if (!System.IO.File.Exists(file)) + { + throw new FileNotFoundException($"File {file} was not found."); + } + } + } + + var eventArgs = new BeforeWriteEventArgs(operationContainer, filesList); + this.BeforeWrite?.Invoke(this, eventArgs); + + if (eventArgs.Cancelled) + { + throw new OperationCanceledException("The Write operation was canceled."); + } + + this.Dal.Session = this; + return filesList; + } } } diff --git a/CDP4DalCommon/CDP4DalCommon.csproj b/CDP4DalCommon/CDP4DalCommon.csproj new file mode 100644 index 000000000..4b6bd99d1 --- /dev/null +++ b/CDP4DalCommon/CDP4DalCommon.csproj @@ -0,0 +1,41 @@ + + + + net47;net471;net472;net48;netstandard2.0;netstandard2.1 + RHEA System S.A. + latest + CDP4DalCommon Community Edition + 26.3.0 + CDP4 Common Class Library that contains common types for any CDP4 server and the CDP4Dal + Copyright © RHEA System S.A. + Sam, Merlin, Alex, Naron, Alexander, Yevhen, Nathanael, Ahmed + CDP4DalCommon-CE + true + https://github.com/RHEAGROUP/COMET-SDK-Community-Edition + true + cdp4-icon.png + true + true + snupkg + https://github.com/RHEAGROUP/COMET-SDK-Community-Edition.git + CDP COMET ECSS-E-TM-10-25 + LGPL-3.0-only + + README.md + true + latest + + + + \ + true + + + \ + true + + + + + + diff --git a/CDP4DalCommon/Tasks/CometTask.cs b/CDP4DalCommon/Tasks/CometTask.cs new file mode 100644 index 000000000..057d4357a --- /dev/null +++ b/CDP4DalCommon/Tasks/CometTask.cs @@ -0,0 +1,119 @@ +// ------------------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2015-2024 RHEA System S.A. +// +// Author: Sam Gerené, Alex Vorobiev, Alexander van Delft, Nathanael Smiechowski, Antoine Théate, Omar Elebiary, Jaime Bernar +// +// This file is part of CDP4-COMET SDK Community Edition +// +// The CDP4-COMET SDK Community Edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 3 of the License, or (at your option) any later version. +// +// The CDP4-COMET SDK Community Edition 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program; if not, write to the Free Software Foundation, +// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// ------------------------------------------------------------------------------------------------------------------------------- + +namespace CDP4DalCommon.Tasks +{ + using System; + + using CDP4Common.DTO; + + /// + /// Represents an operation or task that is the result of a POST request on either the EngineeringModel or SiteDirectory end-points. + /// The class provides information regarding the state of work that is being done. + /// + /// + /// The is a struct to make sure it is immutable and we receive a copy from the services + /// and not a reference to an object + /// + public struct CometTask + { + /// + /// Initializes a new instance of the struct + /// + public CometTask() + { + } + + /// + /// Gets or sets the unique identifier of the + /// + public Guid Id { get; set; } + + /// + /// Gets or sets the request (correlation) token + /// + public string RequestToken { get; set; } + + /// + /// Gets or sets the unique identifier of the that started the + /// + public Guid Actor { get; set; } + + /// + /// Gets or sets the status of the + /// + public StatusKind StatusKind { get; set; } + + /// + /// Gets the duration in seconds for the to complete + /// + /// + /// A value of -1 is returned when the task is still running or not completed with success + /// + public readonly int Duration => this.ComputeDuration(); + + /// + /// Gets or sets the at which the was started + /// + public DateTime? StartedAt { get; set; } = null; + + /// + /// Gets or sets the at which the was finished + /// + public DateTime? FinishedAt { get; set; } = null; + + /// + /// Gets or sets the TopContainer that the is for + /// + public string TopContainer { get; set; } + + /// + /// Gets or sets the revision number that corresponds to the + /// + /// + /// if the value is -1, the task has not (yet) completed with success + /// + public int Revision { get; set; } = -1; + + /// + /// Gets or sets the error in case the operation failed + /// + public string Error { get; set; } = null; + + /// + /// Computes the duration in seconds for the to complete + /// + /// The computated duration + private readonly int ComputeDuration() + { + if (!this.FinishedAt.HasValue || !this.StartedAt.HasValue) + { + return -1; + } + + var timeSpan = this.FinishedAt.Value - this.StartedAt.Value; + return (int)timeSpan.TotalSeconds; + } + } +} diff --git a/CDP4DalCommon/Tasks/StatusKind.cs b/CDP4DalCommon/Tasks/StatusKind.cs new file mode 100644 index 000000000..9f903b951 --- /dev/null +++ b/CDP4DalCommon/Tasks/StatusKind.cs @@ -0,0 +1,55 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2015-2024 RHEA System S.A. +// +// Author: Sam Gerené, Alex Vorobiev, Alexander van Delft, Nathanael Smiechowski, Antoine Théate +// +// This file is part of CDP4-COMET Webservices Community Edition. +// The CDP4-COMET Web Services Community Edition is the RHEA implementation of ECSS-E-TM-10-25 Annex A and Annex C. +// This is an auto-generated class. Any manual changes to this file will be overwritten! +// +// The CDP4-COMET Web Services Community Edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU Affero General Public +// License as published by the Free Software Foundation; either +// version 3 of the License, or (at your option) any later version. +// +// The CDP4-COMET Web Services Community Edition 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace CDP4DalCommon.Tasks +{ + using System.Threading.Tasks; + + /// + /// enumeration datatype that defines the possible states of a + /// + public enum StatusKind + { + /// + /// Assertion that the Task is being processed + /// + PROCESSING, + + /// + /// Assertion that the Task completed with success + /// + SUCCEEDED, + + /// + /// Assertion that the Task failed + /// + FAILED, + + /// + /// Assertion that the Task has been cancelled + /// + CANCELLED + } +} \ No newline at end of file diff --git a/CDP4JsonFileDal.NetCore.Tests/JsonFileDalTestFixture.cs b/CDP4JsonFileDal.NetCore.Tests/JsonFileDalTestFixture.cs index da59d33a8..00a37578a 100644 --- a/CDP4JsonFileDal.NetCore.Tests/JsonFileDalTestFixture.cs +++ b/CDP4JsonFileDal.NetCore.Tests/JsonFileDalTestFixture.cs @@ -558,7 +558,6 @@ public async Task VerifyWriteOfIncompatibleVersionFile() var element2 = readIteration.Element.FirstOrDefault(x => x.ShortName == "ED2"); Assert.That(element2.OrganizationalParticipant.Any(), Is.False); - } [Test] @@ -628,9 +627,19 @@ public async Task VerifyWriteOfCompatibleVersionFile() var element2 = readIteration.Element.FirstOrDefault(x => x.ShortName == "ED2"); Assert.That(element2.OrganizationalParticipant.Count, Is.EqualTo(1)); + } + [Test] + public void VerifyCometTaskOperationNotSupported() + { + var operationContainer = new OperationContainer($"/SiteDirectory/{Guid.NewGuid()}"); - + Assert.Multiple(() => + { + Assert.That(() => this.dal.ReadCometTask(Guid.NewGuid(), CancellationToken.None), Throws.Exception.TypeOf()); + Assert.That(() => this.dal.ReadCometTasks(CancellationToken.None), Throws.Exception.TypeOf()); + Assert.That(() => this.dal.Write(operationContainer, 1), Throws.Exception.TypeOf()); + }); } /// diff --git a/CDP4JsonFileDal.Tests/JsonFileDalTestFixture.cs b/CDP4JsonFileDal.Tests/JsonFileDalTestFixture.cs index 2dbb757b0..406517edb 100644 --- a/CDP4JsonFileDal.Tests/JsonFileDalTestFixture.cs +++ b/CDP4JsonFileDal.Tests/JsonFileDalTestFixture.cs @@ -626,9 +626,19 @@ public async Task VerifyWriteOfCompatibleVersionFile() var element2 = readIteration.Element.FirstOrDefault(x => x.ShortName == "ED2"); Assert.That(element2.OrganizationalParticipant.Count, Is.EqualTo(1)); + } + [Test] + public void VerifyCometTaskOperationNotSupported() + { + var operationContainer = new OperationContainer($"/SiteDirectory/{Guid.NewGuid()}"); - + Assert.Multiple(() => + { + Assert.That(() => this.dal.ReadCometTask(Guid.NewGuid(), CancellationToken.None), Throws.Exception.TypeOf()); + Assert.That(() => this.dal.ReadCometTasks(CancellationToken.None), Throws.Exception.TypeOf()); + Assert.That(() => this.dal.Write(operationContainer, 1), Throws.Exception.TypeOf()); + }); } /// diff --git a/CDP4JsonFileDal/JsonFileDal.cs b/CDP4JsonFileDal/JsonFileDal.cs index 9b6baaa76..cde6bd66e 100644 --- a/CDP4JsonFileDal/JsonFileDal.cs +++ b/CDP4JsonFileDal/JsonFileDal.cs @@ -46,6 +46,8 @@ namespace CDP4JsonFileDal using CDP4Dal.Exceptions; using CDP4Dal.Operations; + using CDP4DalCommon.Tasks; + using CDP4JsonFileDal.Json; using CDP4JsonSerializer; @@ -333,6 +335,42 @@ public override Task> Write(IEnumerable o return Task.FromResult(Enumerable.Empty()); } + /// + /// Write all the s from an asynchronously for a possible long running task. + /// + /// + /// The provided to write + /// + /// The maximum time that we allow the server before responding. If the write operation takes more time + /// than the provided , a + /// + /// The path to the files that need to be uploaded. If is null, then no files are to be uploaded + /// + /// + /// A list of s that has been created or updated since the last Read or Write operation. + /// + public override Task Write(OperationContainer operationContainer, int waitTime, IEnumerable files = null) + { + throw new NotSupportedException("Long Running Task not supported"); + } + + /// + /// Write all the s from an asynchronously. + /// + /// + /// The provided to write + /// + /// + /// The path to the files that need to be uploaded. If is null, then no files are to be uploaded + /// + /// + /// A list of s that has been created or updated since the last Read or Write operation. + /// + public override Task> Write(OperationContainer operationContainer, IEnumerable files = null) + { + throw new NotSupportedException("Writing OperationContainer to the data-source is not supported"); + } + /// /// Find not supported things by model version /// @@ -420,23 +458,6 @@ private void TryRemoveUnlinkedReferences(IEnumerable allDt } } - /// - /// Write all the s from an asynchronously. - /// - /// - /// The provided to write - /// - /// - /// The path to the files that need to be uploaded. If is null, then no files are to be uploaded - /// - /// - /// A list of s that has been created or updated since the last Read or Write operation. - /// - public override Task> Write(OperationContainer operationContainer, IEnumerable files = null) - { - throw new NotSupportedException("Writing OperationContainer to the data-source is not supported"); - } - /// /// Reads the data related to the provided from the data-source /// @@ -567,6 +588,27 @@ public override async Task> Read(CDP4Common.DTO.Iteration ite throw new NotSupportedException("The Read EngineeringModel operation is not supported"); } + /// + /// Reads the identified by the provided + /// + /// The identifier + /// The + /// The read + public override Task ReadCometTask(Guid id, CancellationToken cancellationToken) + { + throw new NotSupportedException("Read CometTask by id not supported"); + } + + /// + /// Reads all available for the current logged + /// + /// The + /// All available + public override Task> ReadCometTasks(CancellationToken cancellationToken) + { + throw new NotSupportedException("Read all available CometTask not supported"); + } + /// /// Retrieves all data necessary for the transfer of a DomainOfExpertise /// diff --git a/CDP4JsonSerializer/Cdp4JsonSerializer.cs b/CDP4JsonSerializer/Cdp4JsonSerializer.cs index 93a6e1106..349731f91 100644 --- a/CDP4JsonSerializer/Cdp4JsonSerializer.cs +++ b/CDP4JsonSerializer/Cdp4JsonSerializer.cs @@ -34,6 +34,7 @@ namespace CDP4JsonSerializer using CDP4JsonSerializer.JsonConverter; using Newtonsoft.Json; + using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; using NLog; @@ -281,6 +282,7 @@ protected JsonSerializer CreateJsonSerializer() serializer.Converters.Add(new ThingSerializer(this.MetaInfoProvider, this.RequestDataModelVersion)); serializer.Converters.Add(new ClasslessDtoSerializer(this.MetaInfoProvider, this.RequestDataModelVersion)); serializer.Converters.Add(new ClassKindConverter()); + serializer.Converters.Add(new StringEnumConverter()); return serializer; } diff --git a/CDP4ServicesDal.NetCore.Tests/CDP4ServicesDal.NetCore.Tests.csproj b/CDP4ServicesDal.NetCore.Tests/CDP4ServicesDal.NetCore.Tests.csproj index 099069574..ed51f420b 100644 --- a/CDP4ServicesDal.NetCore.Tests/CDP4ServicesDal.NetCore.Tests.csproj +++ b/CDP4ServicesDal.NetCore.Tests/CDP4ServicesDal.NetCore.Tests.csproj @@ -34,6 +34,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/CDP4ServicesDal.NetCore.Tests/CdpServicesDalTestFixture.cs b/CDP4ServicesDal.NetCore.Tests/CdpServicesDalTestFixture.cs index 82f73be14..bb7920766 100644 --- a/CDP4ServicesDal.NetCore.Tests/CdpServicesDalTestFixture.cs +++ b/CDP4ServicesDal.NetCore.Tests/CdpServicesDalTestFixture.cs @@ -29,9 +29,12 @@ namespace CDP4ServicesDal.Tests using System.Diagnostics; using System.IO; using System.Linq; + using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; + using System.Text.Json; + using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; @@ -46,8 +49,12 @@ namespace CDP4ServicesDal.Tests using CDP4Dal.Exceptions; using CDP4Dal.Operations; + using CDP4DalCommon.Tasks; + using NUnit.Framework; + using RichardSzalay.MockHttp; + /// /// Suite of tests for the class /// @@ -69,6 +76,7 @@ public class CdpServicesDalTestFixture private SiteReferenceDataLibrary siteReferenceDataLibrary; private ModelReferenceDataLibrary modelReferenceDataLibrary; private ICDPMessageBus messageBus; + private JsonSerializerOptions jsonSerializerOptions; [SetUp] public void Setup() @@ -84,6 +92,12 @@ public void Setup() this.siteDirectory = new SiteDirectory(Guid.Parse("f13de6f8-b03a-46e7-a492-53b2f260f294"), this.session.Assembler.Cache, this.uri); var lazySiteDirectory = new Lazy(() => this.siteDirectory); lazySiteDirectory.Value.Cache.TryAdd(new CacheKey(lazySiteDirectory.Value.Iid, null), lazySiteDirectory); + + this.jsonSerializerOptions = new JsonSerializerOptions() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = { new JsonStringEnumConverter() } + }; this.PopulateSiteDirectory(); } @@ -676,6 +690,181 @@ public async Task Verify_that_person_can_be_Posted() Assert.NotNull(resultPerson); } + [Test] + public async Task VerifyReadCometTask() + { + var mockHttp = new MockHttpMessageHandler(); + var httpClient = mockHttp.ToHttpClient(); + httpClient.BaseAddress = this.uri; + + this.dal = new CdpServicesDal(httpClient); + this.SetDalToBeOpen(this.dal); + + var cometTaskId = Guid.NewGuid(); + + var requestHandler = mockHttp.When($"{CdpServicesDal.CometTaskRoute}/{cometTaskId}"); + + var notFoundHttpResponse = new HttpResponseMessage() + { + StatusCode = HttpStatusCode.NotFound + }; + + requestHandler.Respond(_ => notFoundHttpResponse); + Assert.That(() => this.dal.ReadCometTask(cometTaskId, CancellationToken.None), Throws.Exception.TypeOf()); + + var foundHttpResponse = new HttpResponseMessage(); + requestHandler.Respond(_ => foundHttpResponse); + + var cometTask = new CometTask() + { + Id = cometTaskId, + Actor = Guid.NewGuid(), + FinishedAt = DateTime.UtcNow, + StartedAt = DateTime.UtcNow - TimeSpan.FromSeconds(10), + TopContainer = "SiteDirectory", + StatusKind = StatusKind.SUCCEEDED + }; + + foundHttpResponse.Content = new StringContent(JsonSerializer.Serialize(cometTask, this.jsonSerializerOptions)); + SetHttpHeader(foundHttpResponse, "application/json"); + + var readCometTask = await this.dal.ReadCometTask(cometTaskId, CancellationToken.None); + Assert.That(readCometTask, Is.EqualTo(cometTask)); + + var messagePackHttpResponse = new HttpResponseMessage(); + requestHandler.Respond(_ => messagePackHttpResponse); + messagePackHttpResponse.Content = new StringContent(JsonSerializer.Serialize(cometTask, this.jsonSerializerOptions)); + SetHttpHeader(messagePackHttpResponse, "application/msgpack"); + + Assert.That(() => this.dal.ReadCometTask(cometTaskId, CancellationToken.None), Throws.Exception.TypeOf()); + } + + [Test] + public async Task VerifyReadCometTasks() + { + var mockHttp = new MockHttpMessageHandler(); + var httpClient = mockHttp.ToHttpClient(); + httpClient.BaseAddress = this.uri; + + this.dal = new CdpServicesDal(httpClient); + this.SetDalToBeOpen(this.dal); + + var requestHandler = mockHttp.When($"{CdpServicesDal.CometTaskRoute}"); + + var notFoundHttpResponse = new HttpResponseMessage() + { + StatusCode = HttpStatusCode.NotFound + }; + + requestHandler.Respond(_ => notFoundHttpResponse); + Assert.That(() => this.dal.ReadCometTasks(CancellationToken.None), Throws.Exception.TypeOf()); + + var foundHttpResponse = new HttpResponseMessage(); + requestHandler.Respond(_ => foundHttpResponse); + + var cometTasks = new List() + { + new () + { + Id = Guid.NewGuid(), + Actor = Guid.NewGuid(), + FinishedAt = DateTime.UtcNow, + StartedAt = DateTime.UtcNow - TimeSpan.FromSeconds(10), + TopContainer = "SiteDirectory", + StatusKind = StatusKind.SUCCEEDED + } + }; + + foundHttpResponse.Content = new StringContent(JsonSerializer.Serialize(cometTasks, this.jsonSerializerOptions)); + SetHttpHeader(foundHttpResponse, "application/json"); + + var readCometTasks = await this.dal.ReadCometTasks(CancellationToken.None); + Assert.That(readCometTasks, Is.EquivalentTo(cometTasks)); + + var messagePackHttpResponse = new HttpResponseMessage(); + requestHandler.Respond(_ => messagePackHttpResponse); + messagePackHttpResponse.Content = new StringContent(JsonSerializer.Serialize(cometTasks, this.jsonSerializerOptions)); + SetHttpHeader(messagePackHttpResponse, "application/msgpack"); + + Assert.That(() => this.dal.ReadCometTasks(CancellationToken.None), Throws.Exception.TypeOf()); + } + + [Test] + public async Task VerifyWriteLongRunningTask() + { + var mockHttp = new MockHttpMessageHandler(); + var httpClient = mockHttp.ToHttpClient(); + httpClient.BaseAddress = this.uri; + var operationContainer = new OperationContainer($"/SiteDirectory/{Guid.NewGuid()}"); + this.dal = new CdpServicesDal(httpClient); + + Assert.That(() => this.dal.Write(operationContainer, 1), Throws.InvalidOperationException); + this.SetDalToBeOpen(this.dal); + + Assert.Multiple(() => + { + Assert.That(() => this.dal.Write(null, 1), Throws.ArgumentNullException); + Assert.That(() => this.dal.Write(operationContainer, 0), Throws.Exception.TypeOf()); + }); + + var requestHandler = mockHttp.When(HttpMethod.Post, operationContainer.Context); + + var notFoundHttpResponse = new HttpResponseMessage() + { + StatusCode = HttpStatusCode.NotFound + }; + + requestHandler.Respond(_ => notFoundHttpResponse); + Assert.That(() => this.dal.Write(operationContainer, 1), Throws.Exception.TypeOf()); + + var cometTask = new CometTask() + { + Id = Guid.NewGuid(), + Actor = Guid.NewGuid(), + StartedAt = DateTime.UtcNow - TimeSpan.FromSeconds(1) + }; + + var newCometTaskResponse = new HttpResponseMessage(); + requestHandler.Respond(_ => newCometTaskResponse); + + newCometTaskResponse.Content = new StringContent(JsonSerializer.Serialize(cometTask, this.jsonSerializerOptions)); + SetHttpHeader(newCometTaskResponse, "application/json"); + + var longRunningTaskResult = await this.dal.Write(operationContainer,1); + + Assert.Multiple(() => + { + Assert.That(longRunningTaskResult.IsWaitTimeReached, Is.True); + Assert.That(longRunningTaskResult.Things, Is.Null); + Assert.That(longRunningTaskResult.Task, Is.EqualTo(cometTask)); + }); + + var thingsResponse = new HttpResponseMessage(); + requestHandler.Respond(_ => thingsResponse); + + var stream = new MemoryStream(); + this.dal.Cdp4JsonSerializer.SerializeToStream(this.iteration, stream, true); + stream.Position = 0; + thingsResponse.Content = new StreamContent(stream); + SetHttpHeader(thingsResponse, "application/json"); + + longRunningTaskResult = await this.dal.Write(operationContainer,1); + + Assert.Multiple(() => + { + Assert.That(longRunningTaskResult.IsWaitTimeReached, Is.False); + Assert.That(longRunningTaskResult.Things, Is.Not.Null); + Assert.That(longRunningTaskResult.Task.Id, Is.EqualTo(Guid.Empty)); + }); + + var messagePackResponse = new HttpResponseMessage(); + requestHandler.Respond(_ => messagePackResponse); + + messagePackResponse.Content = new StringContent(JsonSerializer.Serialize(cometTask, this.jsonSerializerOptions)); + SetHttpHeader(messagePackResponse, "application/msgpack"); + Assert.That(() => this.dal.Write(operationContainer, 1), Throws.Exception.TypeOf()); + } + /// /// Set the credentials property so DAL appears to be open /// @@ -687,5 +876,18 @@ private void SetDalToBeOpen(CdpServicesDal dal) var credentialsProperty = typeof(CdpServicesDal).GetProperty("Credentials"); credentialsProperty.SetValue(dal, this.credentials); } + + /// + /// Set correct headers to be validated by the + /// + /// The + /// The content type to add to the http content header + private static void SetHttpHeader(HttpResponseMessage response, string contentType) + { + response.Headers.Add(Headers.CDPServer, "1.0.0"); + response.Headers.Add(Headers.CDPCommon, "1.3.0"); + response.Content.Headers.Remove(Headers.ContentType); + response.Content.Headers.Add(Headers.ContentType, $"{contentType};ecss-e-tm-10-25;version=1.0.0"); + } } } diff --git a/CDP4ServicesDal.Tests/CDP4ServicesDal.Tests.csproj b/CDP4ServicesDal.Tests/CDP4ServicesDal.Tests.csproj index 5f4908c9b..2f75ed419 100644 --- a/CDP4ServicesDal.Tests/CDP4ServicesDal.Tests.csproj +++ b/CDP4ServicesDal.Tests/CDP4ServicesDal.Tests.csproj @@ -34,6 +34,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/CDP4ServicesDal.Tests/CdpServicesDalTestFixture.cs b/CDP4ServicesDal.Tests/CdpServicesDalTestFixture.cs index 784b81cff..8da697ecc 100644 --- a/CDP4ServicesDal.Tests/CdpServicesDalTestFixture.cs +++ b/CDP4ServicesDal.Tests/CdpServicesDalTestFixture.cs @@ -29,6 +29,7 @@ namespace CDP4ServicesDal.Tests using System.Diagnostics; using System.IO; using System.Linq; + using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; @@ -46,8 +47,14 @@ namespace CDP4ServicesDal.Tests using CDP4Dal.Exceptions; using CDP4Dal.Operations; + using CDP4DalCommon.Tasks; + + using Newtonsoft.Json; + using NUnit.Framework; + using RichardSzalay.MockHttp; + /// /// Suite of tests for the class /// @@ -74,7 +81,7 @@ public class CdpServicesDalTestFixture public void Setup() { this.cancelationTokenSource = new CancellationTokenSource(); - + this.credentials = new Credentials("admin", "pass", this.uri); this.dal = new CdpServicesDal(); this.messageBus = new CDPMessageBus(); @@ -84,7 +91,7 @@ public void Setup() this.siteDirectory = new SiteDirectory(Guid.Parse("f13de6f8-b03a-46e7-a492-53b2f260f294"), this.session.Assembler.Cache, this.uri); var lazySiteDirectory = new Lazy(() => this.siteDirectory); lazySiteDirectory.Value.Cache.TryAdd(new CacheKey(lazySiteDirectory.Value.Iid, null), lazySiteDirectory); - + this.PopulateSiteDirectory(); } @@ -680,6 +687,183 @@ public async Task Verify_that_person_can_be_Posted() Assert.NotNull(resultPerson); } + [Test] + public async Task VerifyReadCometTask() + { + var mockHttp = new MockHttpMessageHandler(); + var httpClient = mockHttp.ToHttpClient(); + httpClient.BaseAddress = this.uri; + + this.dal = new CdpServicesDal(httpClient); + this.SetDalToBeOpen(this.dal); + + var cometTaskId = Guid.NewGuid(); + + var requestHandler = mockHttp.When($"{CdpServicesDal.CometTaskRoute}/{cometTaskId}"); + + var notFoundHttpResponse = new HttpResponseMessage() + { + StatusCode = HttpStatusCode.NotFound + }; + + requestHandler.Respond(_ => notFoundHttpResponse); + Assert.That(() => this.dal.ReadCometTask(cometTaskId, CancellationToken.None), Throws.Exception.TypeOf()); + + var foundHttpResponse = new HttpResponseMessage(); + requestHandler.Respond(_ => foundHttpResponse); + + var cometTask = new CometTask() + { + Id = cometTaskId, + Actor = Guid.NewGuid(), + FinishedAt = DateTime.UtcNow, + StartedAt = DateTime.UtcNow - TimeSpan.FromSeconds(10), + TopContainer = "SiteDirectory", + StatusKind = StatusKind.SUCCEEDED + }; + + foundHttpResponse.Content = new StringContent(JsonConvert.SerializeObject(cometTask)); + SetHttpHeader(foundHttpResponse, "application/json"); + + var readCometTask = await this.dal.ReadCometTask(cometTaskId, CancellationToken.None); + Assert.That(readCometTask, Is.EqualTo(cometTask)); + + var messagePackHttpResponse = new HttpResponseMessage(); + requestHandler.Respond(_ => messagePackHttpResponse); + messagePackHttpResponse.Content = new StringContent(JsonConvert.SerializeObject(cometTask)); + SetHttpHeader(messagePackHttpResponse, "application/msgpack"); + + Assert.That(() => this.dal.ReadCometTask(cometTaskId, CancellationToken.None), Throws.Exception.TypeOf()); + } + + [Test] + public async Task VerifyReadCometTasks() + { + var mockHttp = new MockHttpMessageHandler(); + var httpClient = mockHttp.ToHttpClient(); + httpClient.BaseAddress = this.uri; + + this.dal = new CdpServicesDal(httpClient); + this.SetDalToBeOpen(this.dal); + + var requestHandler = mockHttp.When($"{CdpServicesDal.CometTaskRoute}"); + + var notFoundHttpResponse = new HttpResponseMessage() + { + StatusCode = HttpStatusCode.NotFound + }; + + requestHandler.Respond(_ => notFoundHttpResponse); + Assert.That(() => this.dal.ReadCometTasks(CancellationToken.None), Throws.Exception.TypeOf()); + + var foundHttpResponse = new HttpResponseMessage(); + requestHandler.Respond(_ => foundHttpResponse); + + var cometTasks = new List() + { + new CometTask() + { + Id = Guid.NewGuid(), + Actor = Guid.NewGuid(), + FinishedAt = DateTime.UtcNow, + StartedAt = DateTime.UtcNow - TimeSpan.FromSeconds(10), + TopContainer = "SiteDirectory", + StatusKind = StatusKind.SUCCEEDED + } + }; + + foundHttpResponse.Content = new StringContent(JsonConvert.SerializeObject(cometTasks)); + SetHttpHeader(foundHttpResponse, "application/json"); + + var readCometTasks = await this.dal.ReadCometTasks(CancellationToken.None); + Assert.That(readCometTasks, Is.EquivalentTo(cometTasks)); + + var messagePackHttpResponse = new HttpResponseMessage(); + requestHandler.Respond(_ => messagePackHttpResponse); + messagePackHttpResponse.Content = new StringContent(JsonConvert.SerializeObject(cometTasks)); + SetHttpHeader(messagePackHttpResponse, "application/msgpack"); + + Assert.That(() => this.dal.ReadCometTasks(CancellationToken.None), Throws.Exception.TypeOf()); + } + + [Test] + public async Task VerifyWriteLongRunningTask() + { + var mockHttp = new MockHttpMessageHandler(); + var httpClient = mockHttp.ToHttpClient(); + httpClient.BaseAddress = this.uri; + var operationContainer = new OperationContainer($"/SiteDirectory/{Guid.NewGuid()}"); + this.dal = new CdpServicesDal(httpClient); + + Assert.That(() => this.dal.Write(operationContainer, 1), Throws.InvalidOperationException); + this.SetDalToBeOpen(this.dal); + + Assert.Multiple(() => + { + Assert.That(() => this.dal.Write(null, 1), Throws.ArgumentNullException); + Assert.That(() => this.dal.Write(operationContainer, 0), Throws.Exception.TypeOf()); + }); + + var requestHandler = mockHttp.When(HttpMethod.Post, operationContainer.Context); + + var notFoundHttpResponse = new HttpResponseMessage() + { + StatusCode = HttpStatusCode.NotFound + }; + + requestHandler.Respond(_ => notFoundHttpResponse); + notFoundHttpResponse.Content = new StringContent("Unable to proced the write operation"); + + Assert.That(() => this.dal.Write(operationContainer, 1), Throws.Exception.TypeOf()); + + var cometTask = new CometTask() + { + Id = Guid.NewGuid(), + Actor = Guid.NewGuid(), + StartedAt = DateTime.UtcNow - TimeSpan.FromSeconds(1) + }; + + var newCometTaskResponse = new HttpResponseMessage(); + requestHandler.Respond(_ => newCometTaskResponse); + + newCometTaskResponse.Content = new StringContent(JsonConvert.SerializeObject(cometTask)); + SetHttpHeader(newCometTaskResponse, "application/json"); + + var longRunningTaskResult = await this.dal.Write(operationContainer,1); + + Assert.Multiple(() => + { + Assert.That(longRunningTaskResult.IsWaitTimeReached, Is.True); + Assert.That(longRunningTaskResult.Things, Is.Null); + Assert.That(longRunningTaskResult.Task, Is.EqualTo(cometTask)); + }); + + var thingsResponse = new HttpResponseMessage(); + requestHandler.Respond(_ => thingsResponse); + + var stream = new MemoryStream(); + this.dal.Cdp4JsonSerializer.SerializeToStream(this.iteration, stream, true); + stream.Position = 0; + thingsResponse.Content = new StreamContent(stream); + SetHttpHeader(thingsResponse, "application/json"); + + longRunningTaskResult = await this.dal.Write(operationContainer,1); + + Assert.Multiple(() => + { + Assert.That(longRunningTaskResult.IsWaitTimeReached, Is.False); + Assert.That(longRunningTaskResult.Things, Is.Not.Null); + Assert.That(longRunningTaskResult.Task.Id, Is.EqualTo(Guid.Empty)); + }); + + var messagePackResponse = new HttpResponseMessage(); + requestHandler.Respond(_ => messagePackResponse); + + messagePackResponse.Content = new StringContent(JsonConvert.SerializeObject(cometTask)); + SetHttpHeader(messagePackResponse, "application/msgpack"); + Assert.That(() => this.dal.Write(operationContainer, 1), Throws.Exception.TypeOf()); + } + /// /// Set the credentials property so DAL appears to be open /// @@ -691,5 +875,18 @@ private void SetDalToBeOpen(CdpServicesDal dal) var credentialsProperty = typeof(CdpServicesDal).GetProperty("Credentials"); credentialsProperty.SetValue(dal, this.credentials); } + + /// + /// Set correct headers to be validated by the + /// + /// The + /// The content type to add to the http content header + private static void SetHttpHeader(HttpResponseMessage response, string contentType) + { + response.Headers.Add(Headers.CDPServer, "1.0.0"); + response.Headers.Add(Headers.CDPCommon, "1.3.0"); + response.Content.Headers.Remove(Headers.ContentType); + response.Content.Headers.Add(Headers.ContentType, $"{contentType};ecss-e-tm-10-25;version=1.0.0"); + } } } diff --git a/CDP4ServicesDal/CdpServicesDal.cs b/CDP4ServicesDal/CdpServicesDal.cs index 9e9ed50e7..05aec3413 100644 --- a/CDP4ServicesDal/CdpServicesDal.cs +++ b/CDP4ServicesDal/CdpServicesDal.cs @@ -1,21 +1,21 @@ // ------------------------------------------------------------------------------------------------------------------------------- // -// Copyright (c) 2015-2023 RHEA System S.A. -// -// Author: Sam Gerené, Merlin Bieze, Alex Vorobiev, Naron Phou, Alexandervan Delft, Nathanael Smiechowski, Ahmed Abulwafa Ahmed -// -// This file is part of COMET-SDK Community Edition -// -// The COMET-SDK Community Edition is free software; you can redistribute it and/or +// Copyright (c) 2015-2024 RHEA System S.A. +// +// Author: Sam Gerené, Alex Vorobiev, Alexander van Delft, Nathanael Smiechowski, Antoine Théate, Omar Elebiary, Jaime Bernar +// +// This file is part of CDP4-COMET SDK Community Edition +// +// The CDP4-COMET SDK Community Edition is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 3 of the License, or (at your option) any later version. -// -// The COMET-SDK Community Edition is distributed in the hope that it will be useful, +// +// The CDP4-COMET SDK Community Edition 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 // Lesser General Public License for more details. -// +// // You should have received a copy of the GNU Lesser General Public License // along with this program; if not, write to the Free Software Foundation, // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -43,6 +43,8 @@ namespace CDP4ServicesDal using CDP4Common.CommonData; using CDP4Common.DTO; + using CDP4Common.Extensions; + using CDP4DalCommon.Tasks; using CDP4Dal; using CDP4Dal.Composition; @@ -50,7 +52,7 @@ namespace CDP4ServicesDal using CDP4Dal.DAL.ECSS1025AnnexC; using CDP4Dal.Exceptions; using CDP4Dal.Operations; - + using CDP4JsonSerializer; using CDP4MessagePackSerializer; @@ -60,9 +62,6 @@ namespace CDP4ServicesDal using EngineeringModelSetup = CDP4Common.SiteDirectoryData.EngineeringModelSetup; using Thing = CDP4Common.DTO.Thing; using UriExtensions = CDP4Dal.UriExtensions; - using System.Net.NetworkInformation; - - using CDP4Common.Extensions; /// /// The purpose of the is to provide the Data Access Layer for CDP4 ECSS-E-TM-10-25 @@ -74,6 +73,11 @@ namespace CDP4ServicesDal #endif public class CdpServicesDal : Dal { + /// + /// Gets the API route for the + /// + public const string CometTaskRoute = "/tasks"; + /// /// The NLog Logger /// @@ -151,7 +155,7 @@ public override async Task> Write(OperationContainer operatio throw new ArgumentNullException(nameof(operationContainer), $"The {nameof(operationContainer)} may not be null"); } - if (operationContainer.Operations.Count() == 0) + if (!operationContainer.Operations.Any()) { Logger.Debug("The operationContainer is empty, no round trip to the datasource is made"); return Enumerable.Empty(); @@ -165,7 +169,7 @@ public override async Task> Write(OperationContainer operatio { this.OperationContainerFileVerification(operationContainer, files); } - + var attribute = new QueryAttributes { RevisionNumber = operationContainer.TopContainerRevisionNumber @@ -221,6 +225,7 @@ public override async Task> Write(OperationContainer operatio deserializationWatch.Stop(); Guid iterationId; + if (this.TryExtractIterationIdfromUri(httpResponseMessage.RequestMessage.RequestUri, out iterationId)) { this.SetIterationContainer(result, iterationId); @@ -234,6 +239,108 @@ public override async Task> Write(OperationContainer operatio return result; } + /// + /// Write all the s from an asynchronously for a possible long running task. + /// + /// + /// The provided to write + /// + /// The maximum time that we allow the server before responding. If the write operation takes more time + /// than the provided , a + /// + /// The path to the files that need to be uploaded. If is null, then no files are to be uploaded + /// + /// + /// A list of s that has been created or updated since the last Read or Write operation. + /// + /// If the CDP4 DAL is not open + /// If the provided is null + /// If the provided is lower than 1 + public override async Task Write(OperationContainer operationContainer, int waitTime, IEnumerable files = null) + { + if (this.Credentials == null || this.Credentials.Uri == null) + { + throw new InvalidOperationException("The CDP4 DAL is not open."); + } + + VerifyOperationContainerNotNull(operationContainer); + VerifyWaitTimeNotLowerThanOne(waitTime); + + var watch = Stopwatch.StartNew(); + + LongRunningTaskResult result = default; + + if (files != null && files.Any()) + { + this.OperationContainerFileVerification(operationContainer, files); + } + + var attribute = new QueryAttributes + { + RevisionNumber = operationContainer.TopContainerRevisionNumber, + WaitTime = waitTime + }; + + var postToken = operationContainer.Token; + var resourcePath = $"{operationContainer.Context}{attribute}"; + + var uriBuilder = this.GetUriBuilder(this.Credentials.Uri, ref resourcePath); + + Logger.Debug("Resource Path {0}: {1}", postToken, resourcePath); + Logger.Debug("CDP4 Services POST: {0} - {1}", postToken, uriBuilder); + + var requestContent = this.CreateHttpContent(postToken, operationContainer, files); + + var requestsw = Stopwatch.StartNew(); + + using (var httpResponseMessage = await this.httpClient.PostAsync(resourcePath, requestContent)) + { + Logger.Info("CDP4 Services responded in {0} [ms] to POST {1}", requestsw.ElapsedMilliseconds, postToken); + requestsw.Stop(); + + if (httpResponseMessage.StatusCode != HttpStatusCode.OK) + { + var errorResponse = await httpResponseMessage.Content.ReadAsStringAsync(); + var msg = $"The CDP4 Services replied with code {httpResponseMessage.StatusCode}: {httpResponseMessage.ReasonPhrase}: {errorResponse}"; + Logger.Error(msg); + throw new DalWriteException(msg); + } + + this.ProcessHeaders(httpResponseMessage); + + using (var resultStream = await httpResponseMessage.Content.ReadAsStreamAsync()) + { + var deserializationWatch = Stopwatch.StartNew(); + var contentTypeKind = this.QueryContentTypeKind(httpResponseMessage); + + switch (contentTypeKind) + { + case ContentTypeKind.JSON: + Logger.Info("Deserializing JSON response"); + result = this.ExtractResultFromStream(resultStream); + Logger.Info("JSON Deserializer completed in {0} [ms]", deserializationWatch.ElapsedMilliseconds); + break; + case ContentTypeKind.MESSAGEPACK: + throw new NotSupportedException("Long running task not supported with MESSAGEPACK"); + default: + throw new InvalidOperationException( $"ContentTypeKind {contentTypeKind} not supported"); + } + + deserializationWatch.Stop(); + + if (!result.IsWaitTimeReached && this.TryExtractIterationIdfromUri(httpResponseMessage.RequestMessage.RequestUri, out var iterationId)) + { + this.SetIterationContainer(result.Things, iterationId); + } + } + } + + watch.Stop(); + Logger.Info("Write Operation completed in {0} [ms]", watch.ElapsedMilliseconds); + + return result; + } + /// /// Write all the s from all the s. /// @@ -280,6 +387,7 @@ public override async Task> Read(CDP4Common.DTO.Iteration ite // Get the RequiredRdl to load var siteDirectory = this.Session.Assembler.RetrieveSiteDirectory(); var iterationSetup = siteDirectory.Model.SelectMany(mod => mod.IterationSetup).SingleOrDefault(it => it.IterationIid == iteration.Iid); + if (iterationSetup == null) { throw new InvalidOperationException("The Iteration to open does not have any associated IterationSetup."); @@ -385,7 +493,7 @@ public override async Task> Read(IEnumerable() ; + return returned.OfType(); } } } @@ -398,8 +506,8 @@ public override async Task> Read(IEnumerable /// /// an await-able that returns a array. - public override async Task ReadFile(Thing thing, CancellationToken cancellationToken) - { + public override async Task ReadFile(Thing thing, CancellationToken cancellationToken) + { if (this.Credentials == null || this.Credentials.Uri == null) { throw new InvalidOperationException("The CDP4 DAL is not open."); @@ -487,12 +595,12 @@ public override async Task> Read(T thing, CancellationToke { throw new ArgumentNullException(nameof(thing), $"The {nameof(thing)} may not be null"); } - + if (attributes == null) { var includeReferenData = thing is ReferenceDataLibrary; - attributes = this.GetIUriQueryAttribute(includeReferenData); + attributes = GetIUriQueryAttribute(includeReferenData); } var thingRoute = this.CleanUriFinalSlash(thing.Route); @@ -556,6 +664,129 @@ public override async Task> Read(T thing, CancellationToke } } + /// + /// Reads the identified by the provided + /// + /// The identifier + /// The + /// The read + public override async Task ReadCometTask(Guid id, CancellationToken cancellationToken) + { + var resourcePath = $"{CometTaskRoute}/{id}"; + + var readToken = CDP4Common.Helpers.TokenGenerator.GenerateRandomToken(); + var uriBuilder = this.GetUriBuilder(this.Credentials.Uri, ref resourcePath); + + Logger.Debug("Resource Path {0}: {1}", readToken, resourcePath); + Logger.Debug("CDP4Services GET {0}: {1}", readToken, uriBuilder); + + var requestMessage = new HttpRequestMessage(HttpMethod.Get, resourcePath); + requestMessage.Headers.Add(Headers.CDPToken, readToken); + + var requestsw = Stopwatch.StartNew(); + + using (var httpResponseMessage = await this.httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken)) + { + Logger.Info("CDP4 Services responded in {0} [ms] to GET {1}", requestsw.ElapsedMilliseconds, readToken); + requestsw.Stop(); + + if (httpResponseMessage.StatusCode != HttpStatusCode.OK) + { + var msg = $"The data-source replied with code {httpResponseMessage.StatusCode}: {httpResponseMessage.ReasonPhrase}"; + Logger.Error(msg); + throw new DalReadException(msg); + } + + this.ProcessHeaders(httpResponseMessage); + + using (var resultStream = await httpResponseMessage.Content.ReadAsStreamAsync()) + { + var deserializationWatch = Stopwatch.StartNew(); + + CometTask returned; + var contentTypeKind = this.QueryContentTypeKind(httpResponseMessage); + + switch (contentTypeKind) + { + case ContentTypeKind.JSON: + Logger.Info("Deserializing JSON response"); + returned = this.Cdp4JsonSerializer.Deserialize(resultStream); + Logger.Info("JSON Deserializer completed in {0} [ms]", deserializationWatch.ElapsedMilliseconds); + break; + case ContentTypeKind.MESSAGEPACK: + throw new NotSupportedException("Read CometTask by id not supported with MESSAGEPACK"); + default: + throw new InvalidOperationException( $"ContentTypeKind {contentTypeKind} not supported"); + } + + deserializationWatch.Stop(); + + return returned; + } + } + } + + /// + /// Reads all available for the current logged + /// + /// The + /// All available + public override async Task> ReadCometTasks(CancellationToken cancellationToken) + { + var resourcePath = CometTaskRoute; + + var readToken = CDP4Common.Helpers.TokenGenerator.GenerateRandomToken(); + var uriBuilder = this.GetUriBuilder(this.Credentials.Uri, ref resourcePath); + + Logger.Debug("Resource Path {0}: {1}", readToken, resourcePath); + Logger.Debug("CDP4Services GET {0}: {1}", readToken, uriBuilder); + + var requestMessage = new HttpRequestMessage(HttpMethod.Get, resourcePath); + requestMessage.Headers.Add(Headers.CDPToken, readToken); + + var requestsw = Stopwatch.StartNew(); + + using (var httpResponseMessage = await this.httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken)) + { + Logger.Info("CDP4 Services responded in {0} [ms] to GET {1}", requestsw.ElapsedMilliseconds, readToken); + requestsw.Stop(); + + if (httpResponseMessage.StatusCode != HttpStatusCode.OK) + { + var msg = $"The data-source replied with code {httpResponseMessage.StatusCode}: {httpResponseMessage.ReasonPhrase}"; + Logger.Error(msg); + throw new DalReadException(msg); + } + + this.ProcessHeaders(httpResponseMessage); + + using (var resultStream = await httpResponseMessage.Content.ReadAsStreamAsync()) + { + var deserializationWatch = Stopwatch.StartNew(); + + IEnumerable returned; + var contentTypeKind = this.QueryContentTypeKind(httpResponseMessage); + + switch (contentTypeKind) + { + case ContentTypeKind.JSON: + Logger.Info("Deserializing JSON response"); + returned = this.Cdp4JsonSerializer.Deserialize>(resultStream); + Logger.Info("JSON Deserializer completed in {0} [ms]", deserializationWatch.ElapsedMilliseconds); + break; + case ContentTypeKind.MESSAGEPACK: + throw new NotSupportedException("Read all CometTask not supported with MESSAGEPACK"); + default: + throw new InvalidOperationException( $"ContentTypeKind {contentTypeKind} not supported"); + } + + deserializationWatch.Stop(); + + return returned; + } + } + } + /// /// Creates the specified on a data source /// @@ -638,7 +869,7 @@ public override async Task> Open(Credentials credentials, Can Extent = ExtentQueryAttribute.deep, IncludeReferenceData = false }; - + var resourcePath = $"SiteDirectory{queryAttributes}"; var openToken = CDP4Common.Helpers.TokenGenerator.GenerateRandomToken(); @@ -646,7 +877,7 @@ public override async Task> Open(Credentials credentials, Can this.httpClient = this.CreateHttpClient(credentials, this.httpClient); var watch = Stopwatch.StartNew(); - + var uriBuilder = this.GetUriBuilder(credentials.Uri, ref resourcePath); Logger.Debug("Resource Path {0}: {1}", openToken, resourcePath); @@ -657,7 +888,7 @@ public override async Task> Open(Credentials credentials, Can var requestMessage = new HttpRequestMessage(HttpMethod.Get, resourcePath); requestMessage.Headers.Add(Headers.CDPToken, openToken); - using (var httpResponseMessage = await this.httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken: cancellationToken)) + using (var httpResponseMessage = await this.httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken: cancellationToken)) { Logger.Info("CDP4 Services responded in {0} [ms] to Open {1}", requestsw.ElapsedMilliseconds, openToken); requestsw.Stop(); @@ -673,7 +904,7 @@ public override async Task> Open(Credentials credentials, Can Logger.Info("CDP4Services Open {0}: {1} completed in {2} [ms]", openToken, uriBuilder, watch.ElapsedMilliseconds); this.ProcessHeaders(httpResponseMessage); - + using (var resultStream = await httpResponseMessage.Content.ReadAsStreamAsync()) { var deserializationWatch = Stopwatch.StartNew(); @@ -697,6 +928,7 @@ public override async Task> Open(Credentials credentials, Can deserializationWatch.Stop(); var returnedPerson = returned.OfType().SingleOrDefault(x => x.ShortName == credentials.UserName); + if (returnedPerson == null) { throw new InvalidOperationException("User not found."); @@ -746,7 +978,7 @@ public async Task> Open(Credentials credentials, Cancellation /// A collection of s /// The /// A of type of read - public override async Task> CherryPick(Guid engineeringModelId, Guid iterationId, IEnumerable classKinds, + public override async Task> CherryPick(Guid engineeringModelId, Guid iterationId, IEnumerable classKinds, IEnumerable categoriesId, CancellationToken cancellationToken) { var attributes = new QueryAttributes() @@ -800,7 +1032,7 @@ private HttpClient CreateHttpClient(Credentials credentials, HttpClient injected Logger.Debug("creating HttpClient with proxy: {0}", credentials.ProxySettings.Address); var proxy = new WebProxy(credentials.ProxySettings.Address); - + if (!string.IsNullOrEmpty(credentials.ProxySettings.UserName)) { var proxyCredential = new NetworkCredential(credentials.ProxySettings.UserName, credentials.ProxySettings.Password); @@ -812,7 +1044,7 @@ private HttpClient CreateHttpClient(Credentials credentials, HttpClient injected Proxy = proxy, UseProxy = true }; - + result = new HttpClient(httpClientHandler); } @@ -912,6 +1144,7 @@ internal void ConstructPostRequestBodyStream(string token, OperationContainer op { outputStream.CopyTo(memoryStream); memoryStream.Position = 0; + using (var streamReader = new StreamReader(memoryStream)) { var postBody = streamReader.ReadToEnd(); @@ -933,7 +1166,7 @@ internal void ConstructPostRequestBodyStream(string token, OperationContainer op private void ProcessHeaders(HttpResponseMessage httpResponseMessage, bool allowMultiPart = false) { var responseHeaders = httpResponseMessage.Headers; - + var cdpServerHeader = responseHeaders.SingleOrDefault(h => h.Key.ToLower(CultureInfo.InvariantCulture) == Headers.CDPServer.ToLower(CultureInfo.InvariantCulture)); if (cdpServerHeader.Value == null) @@ -1015,7 +1248,7 @@ private bool IsCDP4ContentType(string headerString, bool allowMultiPart) private ContentTypeKind QueryContentTypeKind(HttpResponseMessage httpResponseMessage) { var contentHeaders = httpResponseMessage.Content.Headers; - + var mediaTypeHeader = contentHeaders.SingleOrDefault(h => h.Key.ToLower(CultureInfo.InvariantCulture) == Headers.ContentType.ToLower(CultureInfo.InvariantCulture)); if (mediaTypeHeader.Value == null) @@ -1063,7 +1296,7 @@ public override void Close() this.httpClient.Dispose(); this.httpClient = null; } - + this.CloseSession(); } @@ -1085,7 +1318,7 @@ public override bool IsValidUri(string uri) try { var validUriAssertion = new Uri(uri); - UriExtensions.AssertUriIsHttpOrHttpsSchema(validUriAssertion); + validUriAssertion.AssertUriIsHttpOrHttpsSchema(); return true; } catch (Exception) @@ -1103,7 +1336,7 @@ public override bool IsValidUri(string uri) /// /// the /// - private IQueryAttributes GetIUriQueryAttribute(bool includeReferenceData = false) + private static IQueryAttributes GetIUriQueryAttribute(bool includeReferenceData = false) { return includeReferenceData ? new QueryAttributes @@ -1118,5 +1351,49 @@ private IQueryAttributes GetIUriQueryAttribute(bool includeReferenceData = false IncludeAllContainers = true }; } + + /// + /// Extracts the from the JSON payload contained in a + /// + /// The that contains the JSON payload + /// The extracted + private LongRunningTaskResult ExtractResultFromStream(Stream stream) + { + using (var reader = new StreamReader(stream)) + { + var firstChar = (char)reader.Peek(); + stream.Position = 0; + + return firstChar == '[' + ? new LongRunningTaskResult(this.Cdp4JsonSerializer.Deserialize(stream)) + : new LongRunningTaskResult(this.Cdp4JsonSerializer.Deserialize(stream)); + } + } + + /// + /// Verifies that the provided is not lower than 1 + /// + /// The wait time value to verify + /// If the is lower than 1 + private static void VerifyWaitTimeNotLowerThanOne(int waitTime) + { + if (waitTime < 1) + { + throw new ArgumentOutOfRangeException(nameof(waitTime), $"The {nameof(waitTime)} may not be lower than 1"); + } + } + + /// + /// Verifies that the provided is not null + /// + /// The to verify + /// If the provided is null + private static void VerifyOperationContainerNotNull(OperationContainer operationContainer) + { + if (operationContainer == null) + { + throw new ArgumentNullException(nameof(operationContainer), $"The {nameof(operationContainer)} may not be null"); + } + } } } diff --git a/CDP4WspDal.NetCore.Tests/WSPDalTestFixture.cs b/CDP4WspDal.NetCore.Tests/WSPDalTestFixture.cs index cc342757d..19f356f88 100644 --- a/CDP4WspDal.NetCore.Tests/WSPDalTestFixture.cs +++ b/CDP4WspDal.NetCore.Tests/WSPDalTestFixture.cs @@ -532,6 +532,19 @@ public async Task Vefify_that_when_OperationContainer_is_empty_an_empty_is_retur Assert.That(await dal.Write(operationContainer), Is.Empty); } + [Test] + public void VerifyCometTaskOperationNotSupported() + { + var operationContainer = new OperationContainer($"/SiteDirectory/{Guid.NewGuid()}"); + + Assert.Multiple(() => + { + Assert.That(() => this.dal.ReadCometTask(Guid.NewGuid(), CancellationToken.None), Throws.Exception.TypeOf()); + Assert.That(() => this.dal.ReadCometTasks(CancellationToken.None), Throws.Exception.TypeOf()); + Assert.That(() => this.dal.Write(operationContainer, 1), Throws.Exception.TypeOf()); + }); + } + /// /// Set the credentials property so DAL appears to be open /// diff --git a/CDP4WspDal.Tests/WSPDalTestFixture.cs b/CDP4WspDal.Tests/WSPDalTestFixture.cs index d3d19de9f..025de3d3c 100644 --- a/CDP4WspDal.Tests/WSPDalTestFixture.cs +++ b/CDP4WspDal.Tests/WSPDalTestFixture.cs @@ -572,6 +572,19 @@ public void VerifyCherryPick() new List(), CancellationToken.None), Throws.Exception.TypeOf()); } + [Test] + public void VerifyCometTaskOperationNotSupported() + { + var operationContainer = new OperationContainer($"/SiteDirectory/{Guid.NewGuid()}"); + + Assert.Multiple(() => + { + Assert.That(() => this.dal.ReadCometTask(Guid.NewGuid(), CancellationToken.None), Throws.Exception.TypeOf()); + Assert.That(() => this.dal.ReadCometTasks(CancellationToken.None), Throws.Exception.TypeOf()); + Assert.That(() => this.dal.Write(operationContainer, 1), Throws.Exception.TypeOf()); + }); + } + /// /// Set the credentials property so DAL appears to be open /// diff --git a/CDP4WspDal/WSPDal.cs b/CDP4WspDal/WSPDal.cs index ca5a279cf..8c80db5c1 100644 --- a/CDP4WspDal/WSPDal.cs +++ b/CDP4WspDal/WSPDal.cs @@ -49,7 +49,9 @@ namespace CDP4WspDal using CDP4Dal.DAL.ECSS1025AnnexC; using CDP4Dal.Exceptions; using CDP4Dal.Operations; - + + using CDP4DalCommon.Tasks; + using CDP4JsonSerializer; using NLog; @@ -232,6 +234,25 @@ public override Task> Write(IEnumerable o throw new NotSupportedException("Writing multiple OperationContainers to the data-source is not supported"); } + /// + /// Write all the s from an asynchronously for a possible long running task. + /// + /// + /// The provided to write + /// + /// The maximum time that we allow the server before responding. If the write operation takes more time + /// than the provided , a + /// + /// The path to the files that need to be uploaded. If is null, then no files are to be uploaded + /// + /// + /// A list of s that has been created or updated since the last Read or Write operation. + /// + public override Task Write(OperationContainer operationContainer, int waitTime, IEnumerable files = null) + { + throw new NotSupportedException("Long Running Task not supported"); + } + /// /// Reads the data related to the provided from the data-source /// @@ -398,6 +419,27 @@ public override async Task> Read(T thing, CancellationToke } } } + + /// + /// Reads the identified by the provided + /// + /// The identifier + /// The + /// The read + public override Task ReadCometTask(Guid id, CancellationToken cancellationToken) + { + throw new NotSupportedException("Read CometTask by id not supported"); + } + + /// + /// Reads all available for the current logged + /// + /// The + /// All available + public override Task> ReadCometTasks(CancellationToken cancellationToken) + { + throw new NotSupportedException("Read all available CometTask not supported"); + } /// /// Creates the specified on a data source