diff --git a/CDP4Dal.Tests/SessionTestFixture.cs b/CDP4Dal.Tests/SessionTestFixture.cs index 20418ff88..6d5025f0d 100644 --- a/CDP4Dal.Tests/SessionTestFixture.cs +++ b/CDP4Dal.Tests/SessionTestFixture.cs @@ -1,8 +1,8 @@ // -------------------------------------------------------------------------------------------------------------------- // -// Copyright (c) 2015-2019 RHEA System S.A. +// Copyright (c) 2015-2020 RHEA System S.A. // -// Author: Sam Gerené, Merlin Bieze, Alex Vorobiev, Naron Phou +// Author: Sam Gerené, Merlin Bieze, Alex Vorobiev, Naron Phou, Alexander van Delft // // This file is part of CDP4-SDK Community Edition // @@ -29,20 +29,25 @@ namespace CDP4Dal.Tests using System.Linq; using System.Threading; using System.Threading.Tasks; + using CDP4Common.CommonData; - using CDP4Common.DTO; using CDP4Common.MetaInfo; - using CDP4Dal.Operations; using CDP4Common.SiteDirectoryData; using CDP4Common.Types; + + using CDP4Dal.Operations; using CDP4Dal.Composition; using CDP4Dal.DAL; using CDP4Dal.Events; + using Moq; + using NUnit.Framework; using DomainOfExpertise = CDP4Common.SiteDirectoryData.DomainOfExpertise; + using EngineeringModel = CDP4Common.DTO.EngineeringModel; using EngineeringModelSetup = CDP4Common.DTO.EngineeringModelSetup; + using Iteration = CDP4Common.DTO.Iteration; using ModelReferenceDataLibrary = CDP4Common.SiteDirectoryData.ModelReferenceDataLibrary; using SiteDirectory = CDP4Common.DTO.SiteDirectory; using SiteReferenceDataLibrary = CDP4Common.SiteDirectoryData.SiteReferenceDataLibrary; @@ -313,7 +318,7 @@ public async Task VerifyThatSiteRdlRequiredByModelRdlCannotBeClosed() var modelsetup = new EngineeringModelSetup(Guid.NewGuid(), 0); modelsetup.RequiredRdl.Add(mrdl.Iid); - var model = new EngineeringModel(Guid.NewGuid(), 0) { EngineeringModelSetup = modelsetup.Iid }; + var model = new CDP4Common.DTO.EngineeringModel(Guid.NewGuid(), 0) { EngineeringModelSetup = modelsetup.Iid }; var iteration = new Iteration(Guid.NewGuid(), 0); model.Iteration.Add(iteration.Iid); @@ -571,6 +576,35 @@ public void VeriyThatCDPVersionIsSet() Assert.AreEqual(version.Build, this.session.DalVersion.Build); } + [Test] + public async Task VeriyThatCanCancelWorks() + { + var iterationSetup = new CDP4Common.SiteDirectoryData.IterationSetup(Guid.NewGuid(), null, null) { FrozenOn = DateTime.Now, IterationIid = Guid.NewGuid() }; + var JohnDoe = new CDP4Common.SiteDirectoryData.Person(this.person.Iid, this.session.Assembler.Cache, this.uri) { ShortName = "John" }; + var activeDomain = new DomainOfExpertise(Guid.NewGuid(), null, null); + var model = new EngineeringModel(Guid.NewGuid(), 1); + var iteration = new Iteration(Guid.NewGuid(), 10) { IterationSetup = iterationSetup.Iid }; + + var iterationToOpen = new CDP4Common.EngineeringModelData.Iteration(iteration.Iid, null, null); + var modelToOpen = new CDP4Common.EngineeringModelData.EngineeringModel(model.Iid, null, null); + + this.session.GetType().GetProperty("ActivePerson").SetValue(this.session, JohnDoe, null); + + iterationToOpen.Container = modelToOpen; + + Assert.IsFalse(this.session.CanCancel()); + + this.mockedDal.Setup(x => x.Read(It.IsAny(), It.IsAny(), null)) + .Callback(() => Assert.IsTrue(this.session.CanCancel())) + .ReturnsAsync(new List()); + + await this.session.Read(iterationToOpen, activeDomain); + + Assert.IsFalse(this.session.CanCancel()); + + this.mockedDal.Verify(x => x.Read(It.IsAny(), It.IsAny(), null), Times.Exactly(1)); + } + [Test] public void VerifyThatIsVersionSupportedReturnsExpectedResult() { @@ -587,8 +621,136 @@ public void VerifyThatIsVersionSupportedReturnsExpectedResult() var notSupportedVersion = new Version("2.0.0"); Assert.IsFalse(this.session.IsVersionSupported(notSupportedVersion)); } - } + [Test] + public async Task VerifyThatQueryCurrentDomainOfExpertiseWorks() + { + var siteDir = new CDP4Common.SiteDirectoryData.SiteDirectory(Guid.NewGuid(), this.session.Assembler.Cache, this.uri); + var JohnDoe = new CDP4Common.SiteDirectoryData.Person(this.person.Iid, this.session.Assembler.Cache, this.uri) { ShortName = "John" }; + var modelSetup = new CDP4Common.SiteDirectoryData.EngineeringModelSetup(Guid.NewGuid(), this.session.Assembler.Cache, this.uri); + var iterationSetup = new CDP4Common.SiteDirectoryData.IterationSetup(Guid.NewGuid(), this.session.Assembler.Cache, this.uri) { IterationIid = Guid.NewGuid() }; + var mrdl = new ModelReferenceDataLibrary(Guid.NewGuid(), this.session.Assembler.Cache, this.uri); + var srdl = new SiteReferenceDataLibrary(Guid.NewGuid(), this.session.Assembler.Cache, this.uri); + var activeDomain = new DomainOfExpertise(Guid.NewGuid(), this.session.Assembler.Cache, this.uri); + mrdl.RequiredRdl = srdl; + modelSetup.RequiredRdl.Add(mrdl); + modelSetup.IterationSetup.Add(iterationSetup); + siteDir.Model.Add(modelSetup); + siteDir.SiteReferenceDataLibrary.Add(srdl); + siteDir.Domain.Add(activeDomain); + siteDir.Person.Add(JohnDoe); + + this.session.Assembler.Cache.TryAdd(new CacheKey(siteDir.Iid, null), new Lazy(() => siteDir)); + this.session.Assembler.Cache.TryAdd(new CacheKey(JohnDoe.Iid, null), new Lazy(() => JohnDoe)); + this.session.Assembler.Cache.TryAdd(new CacheKey(modelSetup.Iid, null), new Lazy(() => modelSetup)); + this.session.Assembler.Cache.TryAdd(new CacheKey(mrdl.Iid, null), new Lazy(() => mrdl)); + this.session.Assembler.Cache.TryAdd(new CacheKey(srdl.Iid, null), new Lazy(() => srdl)); + this.session.Assembler.Cache.TryAdd(new CacheKey(siteDir.Iid, null), new Lazy(() => siteDir)); + this.session.Assembler.Cache.TryAdd(new CacheKey(iterationSetup.Iid, null), new Lazy(() => iterationSetup)); + + this.session.GetType().GetProperty("ActivePerson").SetValue(this.session, JohnDoe, null); + + var participant = new CDP4Common.SiteDirectoryData.Participant(Guid.NewGuid(), this.session.Assembler.Cache, this.uri) { Person = this.session.ActivePerson }; + participant.Domain.Add(activeDomain); + modelSetup.Participant.Add(participant); + + var model = new EngineeringModel(Guid.NewGuid(), 1); + var iteration = new Iteration(iterationSetup.IterationIid, 10) { IterationSetup = iterationSetup.Iid }; + model.Iteration.Add(iteration.Iid); + model.EngineeringModelSetup = modelSetup.Iid; + + var readOutput = new List + { + model, + iteration + }; + + var readTaskCompletionSource = new TaskCompletionSource>(); + readTaskCompletionSource.SetResult(readOutput); + this.mockedDal.Setup(x => x.Read(It.IsAny(), It.IsAny(), It.IsAny())).Returns(readTaskCompletionSource.Task); + + var iterationToOpen = new CDP4Common.EngineeringModelData.Iteration(iteration.Iid, null, null); + var modelToOpen = new CDP4Common.EngineeringModelData.EngineeringModel(model.Iid, null, null); + iterationToOpen.Container = modelToOpen; + + await this.session.Read(iterationToOpen, activeDomain); + + var resultDomain = this.session.QueryCurrentDomainOfExpertise(); + + Assert.AreEqual(activeDomain, resultDomain); + + iterationSetup.FrozenOn = DateTime.UtcNow; + + resultDomain = this.session.QueryCurrentDomainOfExpertise(); + + Assert.IsNull(resultDomain); + } + + [Test] + public async Task VerifyThatQueryDomainOfExpertiseWorks() + { + var siteDir = new CDP4Common.SiteDirectoryData.SiteDirectory(Guid.NewGuid(), this.session.Assembler.Cache, this.uri); + var JohnDoe = new CDP4Common.SiteDirectoryData.Person(this.person.Iid, this.session.Assembler.Cache, this.uri) { ShortName = "John" }; + var modelSetup = new CDP4Common.SiteDirectoryData.EngineeringModelSetup(Guid.NewGuid(), this.session.Assembler.Cache, this.uri); + var iterationSetup = new CDP4Common.SiteDirectoryData.IterationSetup(Guid.NewGuid(), this.session.Assembler.Cache, this.uri) { IterationIid = Guid.NewGuid() }; + var mrdl = new ModelReferenceDataLibrary(Guid.NewGuid(), this.session.Assembler.Cache, this.uri); + var srdl = new SiteReferenceDataLibrary(Guid.NewGuid(), this.session.Assembler.Cache, this.uri); + var activeDomain = new DomainOfExpertise(Guid.NewGuid(), this.session.Assembler.Cache, this.uri); + mrdl.RequiredRdl = srdl; + modelSetup.RequiredRdl.Add(mrdl); + modelSetup.IterationSetup.Add(iterationSetup); + siteDir.Model.Add(modelSetup); + siteDir.SiteReferenceDataLibrary.Add(srdl); + siteDir.Domain.Add(activeDomain); + siteDir.Person.Add(JohnDoe); + + this.session.Assembler.Cache.TryAdd(new CacheKey(siteDir.Iid, null), new Lazy(() => siteDir)); + this.session.Assembler.Cache.TryAdd(new CacheKey(JohnDoe.Iid, null), new Lazy(() => JohnDoe)); + this.session.Assembler.Cache.TryAdd(new CacheKey(modelSetup.Iid, null), new Lazy(() => modelSetup)); + this.session.Assembler.Cache.TryAdd(new CacheKey(mrdl.Iid, null), new Lazy(() => mrdl)); + this.session.Assembler.Cache.TryAdd(new CacheKey(srdl.Iid, null), new Lazy(() => srdl)); + this.session.Assembler.Cache.TryAdd(new CacheKey(siteDir.Iid, null), new Lazy(() => siteDir)); + this.session.Assembler.Cache.TryAdd(new CacheKey(iterationSetup.Iid, null), new Lazy(() => iterationSetup)); + + this.session.GetType().GetProperty("ActivePerson").SetValue(this.session, JohnDoe, null); + + var participant = new CDP4Common.SiteDirectoryData.Participant(Guid.NewGuid(), this.session.Assembler.Cache, this.uri){ Person = this.session.ActivePerson }; + participant.Domain.Add(activeDomain); + modelSetup.Participant.Add(participant); + + var model = new EngineeringModel(Guid.NewGuid(), 1); + var iteration = new Iteration(iterationSetup.IterationIid, 10) { IterationSetup = iterationSetup.Iid }; + model.Iteration.Add(iteration.Iid); + model.EngineeringModelSetup = modelSetup.Iid; + + var readOutput = new List + { + model, + iteration + }; + + var readTaskCompletionSource = new TaskCompletionSource>(); + readTaskCompletionSource.SetResult(readOutput); + this.mockedDal.Setup(x => x.Read(It.IsAny(), It.IsAny(), It.IsAny())).Returns(readTaskCompletionSource.Task); + + var iterationToOpen = new CDP4Common.EngineeringModelData.Iteration(iteration.Iid, null, null); + var modelToOpen = new CDP4Common.EngineeringModelData.EngineeringModel(model.Iid, null, null); + iterationToOpen.Container = modelToOpen; + + await this.session.Read(iterationToOpen, activeDomain); + + var resultDomains = this.session.QueryDomainOfExpertise(); + + CollectionAssert.AreEqual(new [] {activeDomain}, resultDomains); + + iterationSetup.FrozenOn = DateTime.UtcNow; + + resultDomains = this.session.QueryDomainOfExpertise(); + + CollectionAssert.IsEmpty(resultDomains); + } + } + [DalExport("test dal", "test dal description", "1.1.0", DalType.Web)] internal class TestDal : IDal { diff --git a/CDP4Dal/ISession.cs b/CDP4Dal/ISession.cs index d7d079945..5f77c2e74 100644 --- a/CDP4Dal/ISession.cs +++ b/CDP4Dal/ISession.cs @@ -275,6 +275,12 @@ public interface ISession /// Task Close(); + /// + /// Can a Cancel action be executed? + /// + /// True is Cancel is allowed, otherwise false. + bool CanCancel(); + /// /// Cancel any Read or Open operation. /// @@ -305,5 +311,21 @@ public interface ISession /// The . /// Task CloseModelRdl(ModelReferenceDataLibrary modelRdl); + + /// + /// Queries the current from the session for the current + /// + /// + /// The if selected, null otherwise. + /// + DomainOfExpertise QueryCurrentDomainOfExpertise(); + + /// + /// Queries the 's 's from the session for the current + /// + /// + /// The if selected, null otherwise. + /// + IEnumerable QueryDomainOfExpertise(); } } diff --git a/CDP4Dal/Session.cs b/CDP4Dal/Session.cs index c8d769259..e6b7d4d40 100644 --- a/CDP4Dal/Session.cs +++ b/CDP4Dal/Session.cs @@ -383,7 +383,8 @@ public async Task Read(Iteration iteration, DomainOfExpertise domain) { var iterationDto = (CDP4Common.DTO.Iteration) iteration.ToDto(); this.Dal.Session = this; - dtoThings = await this.Dal.Read(iterationDto, this.cancellationTokenSource.Token); + dtoThings = await this.Dal.Read(iterationDto, this.cancellationTokenSource.Token, null); + this.cancellationTokenSource.Token.ThrowIfCancellationRequested(); } catch (OperationCanceledException) { @@ -391,6 +392,10 @@ public async Task Read(Iteration iteration, DomainOfExpertise domain) this.cancellationTokenSource = null; return; } + finally + { + this.cancellationTokenSource = null; + } // proceed if no problem var enumerable = dtoThings as IList ?? dtoThings.ToList(); @@ -686,12 +691,26 @@ public async Task Reload() } } + /// + /// Can a Cancel action be executed? + /// + /// True is Cancel is allowed, otherwise false. + public bool CanCancel() + { + if (this.cancellationTokenSource == null) + { + return false; + } + + return this.cancellationTokenSource.Token.CanBeCanceled && !this.cancellationTokenSource.IsCancellationRequested; + } + /// /// Cancel any Read or Open operation. /// public void Cancel() { - if (this.cancellationTokenSource != null) + if (this.CanCancel()) { this.cancellationTokenSource.Cancel(); } @@ -928,5 +947,42 @@ private void AddIterationToOpenList(Guid iterationId, DomainOfExpertise activeDo var modelRdl = ((EngineeringModel) iteration.Container).EngineeringModelSetup.RequiredRdl.Single(); this.AddRdlToOpenList(modelRdl); } + + /// + /// Queries the current from the session for the current + /// + /// + /// The if selected, null otherwise. + /// + public DomainOfExpertise QueryCurrentDomainOfExpertise() + { + var iterationDomainPair = this.OpenIterations.SingleOrDefault(x => !x.Key.IterationSetup.FrozenOn.HasValue); + + if (iterationDomainPair.Equals(default(KeyValuePair>))) + { + return null; + } + + return (iterationDomainPair.Value == null) || (iterationDomainPair.Value.Item1 == null) ? null : iterationDomainPair.Value.Item1; + } + + /// + /// Queries the 's 's from the session for the current + /// + /// + /// The if selected, null otherwise. + /// + public IEnumerable QueryDomainOfExpertise() + { + var iterationDomainPair = this.OpenIterations.SingleOrDefault(x => !x.Key.IterationSetup.FrozenOn.HasValue); + var domainOfExpertise = new List(); + + if (iterationDomainPair.Value?.Item2 != null) + { + domainOfExpertise.AddRange(iterationDomainPair.Value.Item2.Domain); + } + + return domainOfExpertise; + } } } diff --git a/CDP4ServicesDal/CDP4ServicesDal.csproj b/CDP4ServicesDal/CDP4ServicesDal.csproj index d35377284..1b2544a17 100644 --- a/CDP4ServicesDal/CDP4ServicesDal.csproj +++ b/CDP4ServicesDal/CDP4ServicesDal.csproj @@ -34,6 +34,10 @@ + + + + diff --git a/CDP4ServicesDal/CdpServicesDal.cs b/CDP4ServicesDal/CdpServicesDal.cs index 3d5b40b24..a6f2ed985 100644 --- a/CDP4ServicesDal/CdpServicesDal.cs +++ b/CDP4ServicesDal/CdpServicesDal.cs @@ -38,7 +38,6 @@ namespace CDP4ServicesDal using System.Net.Http; using System.Net.Http.Headers; using System.Text; - using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -57,6 +56,7 @@ namespace CDP4ServicesDal using EngineeringModelSetup = CDP4Common.SiteDirectoryData.EngineeringModelSetup; using Thing = CDP4Common.DTO.Thing; + using UriExtensions = CDP4Dal.UriExtensions; /// /// The purpose of the is to provide the Data Access Layer for CDP4 ECSS-E-TM-10-25 @@ -298,47 +298,23 @@ public override async Task ReadFile(Thing thing, CancellationToken cance throw new DalReadException(msg); } - this.ProcessHeaders(httpResponseMessage); - - var multipartContent = await httpResponseMessage.Content.ReadAsByteArrayAsync(); + this.ProcessHeaders(httpResponseMessage, true); - var returned = this.GetFileContent(multipartContent); + cancellationToken.ThrowIfCancellationRequested(); - watch.Stop(); - Logger.Info("JSON Deserializer completed in {0} [ms]", watch.ElapsedMilliseconds); + var multipartContent = await httpResponseMessage.Content.ReadAsMultipartAsync(cancellationToken); - return returned; - } - } - - /// - /// Gets the File part of a multipart Response - /// - /// - /// Physical file part of a that contains both JSON and binary data - private byte[] GetFileContent(byte[] responseBody) - { - var responseBodyString = Encoding.Default.GetString(responseBody); + cancellationToken.ThrowIfCancellationRequested(); - var boundaryRegex = new Regex("^-*\\w+"); - var boundary = boundaryRegex.Matches(responseBodyString); + var returned = await multipartContent.Contents[1].ReadAsByteArrayAsync(); - var regexWithBoundaryAtTheEnd = new Regex("Content-Length:\\s\\d+(\\r\\n|\\r|\\n){2}([\\s\\S]*)(\\r\\n)" + boundary[0], RegexOptions.IgnoreCase); - var regexWithoutBoundaryAtTheEnd = new Regex("Content-Length:\\s\\d+(\\r\\n|\\r|\\n){2}([\\s\\S]*)", RegexOptions.IgnoreCase); + cancellationToken.ThrowIfCancellationRequested(); - var body = regexWithBoundaryAtTheEnd.Matches(responseBodyString); - - if (body.Count == 0) - { - body = regexWithoutBoundaryAtTheEnd.Matches(responseBodyString); - } + watch.Stop(); + Logger.Info("JSON Deserializer completed in {0} [ms]", watch.ElapsedMilliseconds); - if (body.Count == 0) - { - return null; + return returned; } - - return Encoding.Default.GetBytes(body[0].Groups[2].Value); } /// @@ -711,20 +687,23 @@ internal void ConstructPostRequestBodyStream(string token, OperationContainer op /// /// process the response to verify that the required headers are available /// - /// + /// /// The that is to be verified /// - private void ProcessHeaders(HttpResponseMessage httpResponseMessage) + /// Optional indicating if multipart/mixed content is allowed in the contentheader + 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) { throw new HeaderException($"Header {Headers.CDPServer} not found"); } var cdpCommonHeader = responseHeaders.SingleOrDefault(h => h.Key.ToLower(CultureInfo.InvariantCulture) == Headers.CDPCommon.ToLower(CultureInfo.InvariantCulture)); + if (cdpCommonHeader.Value == null) { throw new HeaderException($"Header {Headers.CDPCommon} not found"); @@ -733,17 +712,48 @@ private void ProcessHeaders(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) { throw new HeaderException($"Header {Headers.ContentType} not found"); } - - if (Convert.ToString(mediaTypeHeader.Value.FirstOrDefault()).ToLower(CultureInfo.InvariantCulture) != "application/json; ecss-e-tm-10-25; version=1.0.0") + + var headerString = Convert.ToString(mediaTypeHeader.Value.FirstOrDefault()).ToLower(CultureInfo.InvariantCulture); + + if (!this.IsCDP4ContentType(headerString, allowMultiPart)) { throw new HeaderException($"Header Media-Type has incompatible value: {mediaTypeHeader.Value} "); } } - + + /// + /// Checks message content header for CDP4 tags an compatiple content-types + /// + /// The header + /// Indication if multipart Content-Type is allowed + /// true if a CDP4 content type is found, otherwise false + private bool IsCDP4ContentType(string headerString, bool allowMultiPart) + { + var headerArray = headerString + .Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => x.Trim()).ToArray(); + + if (!headerArray.Contains("ecss-e-tm-10-25") || !headerArray.Contains("version=1.0.0")) + { + return false; + } + + if (!headerArray.Contains("application/json")) + { + if (!allowMultiPart || !headerArray.Contains("multipart/mixed")) + { + return false; + } + } + + return true; + } + /// /// Close the ///