From d21c8c9f6d059ac64a7d5d26a7f07e1f759e56df Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Wed, 16 Jul 2025 18:09:20 -0400 Subject: [PATCH] whatever --- .../src/app/Mocha.Oms.Server/Program.cs | 16 ++ .../Commands/ElementCommand.cs | 5 + .../src/lib/Mocha.Core/KnownInstanceGuids.cs | 3 + mocha-dotnet/src/lib/Mocha.Core/Oms.cs | 134 +++++++++++++--- .../src/lib/Mocha.Core/OmsMethodBuilder.cs | 9 +- mocha-dotnet/src/lib/Mocha.Core/OmsStorage.cs | 87 +++++++++++ .../Mocha.Core/Storage/BinaryOmsStorage.cs | 138 ++++++++++++++++ .../lib/Mocha.Core/Storage/JsonOmsStorage.cs | 147 ++++++++++++++++++ .../Mocha.Core/Storage/StreamOmsStorage.cs | 40 +++++ .../src/lib/Mocha.Testing/OmsTestsBase.cs | 11 +- .../tests/Mocha.Core.Tests/BasicTests.cs | 9 +- .../EvaluateBooleanExpressionMethodTests.cs | 2 +- .../Mocha.Core.Tests/RelationshipTests.cs | 34 +++- .../Storage/JsonStorageTests.cs | 70 +++++++++ 14 files changed, 677 insertions(+), 28 deletions(-) create mode 100644 mocha-dotnet/src/lib/Mocha.Core/OmsStorage.cs create mode 100644 mocha-dotnet/src/lib/Mocha.Core/Storage/BinaryOmsStorage.cs create mode 100644 mocha-dotnet/src/lib/Mocha.Core/Storage/JsonOmsStorage.cs create mode 100644 mocha-dotnet/src/lib/Mocha.Core/Storage/StreamOmsStorage.cs create mode 100644 mocha-dotnet/tests/Mocha.Core.Tests/Storage/JsonStorageTests.cs diff --git a/mocha-dotnet/src/app/Mocha.Oms.Server/Program.cs b/mocha-dotnet/src/app/Mocha.Oms.Server/Program.cs index bd1b610..4e27e7d 100644 --- a/mocha-dotnet/src/app/Mocha.Oms.Server/Program.cs +++ b/mocha-dotnet/src/app/Mocha.Oms.Server/Program.cs @@ -10,6 +10,7 @@ using Mocha.Core; using Mocha.Core.UI.Server; using Mocha.Core.Logging; using Mocha.Core.Logging.Loggers; +using Mocha.Core.Storage; /// /// Provides the entry point for a simple application which starts a Web server @@ -56,6 +57,19 @@ public class Program : WebApplication Console.Error.WriteLine("oms: error: failed to write PID file; graceful control will be unavailable"); } + string mdtfile = "/etc/mocha/oms/20250708.mdt"; + mdtfile = Path.Combine(new string[] { System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "mocha", DateTime.Now.ToString("yyyyMMddHHmmss") + ".mdt" }); + + string? workdir = Path.GetDirectoryName(mdtfile); + if (workdir != null) + { + if (!Directory.Exists(workdir)) + { + Directory.CreateDirectory(workdir); + } + } + Oms.Storages.Add(new BinaryOmsStorage(mdtfile)); + DefaultFileLogger logger = new DefaultFileLogger(); ConsoleLogger logger2 = new ConsoleLogger(); Log.Loggers.Add(logger); @@ -122,6 +136,8 @@ public class Program : WebApplication UpdateMochafile(); + Oms.ReloadStorages(); + // now we can start the Web server base.OnStartup(e); } diff --git a/mocha-dotnet/src/lib/Mocha.Core.UI.Server/Commands/ElementCommand.cs b/mocha-dotnet/src/lib/Mocha.Core.UI.Server/Commands/ElementCommand.cs index 0a330b9..dd1116b 100644 --- a/mocha-dotnet/src/lib/Mocha.Core.UI.Server/Commands/ElementCommand.cs +++ b/mocha-dotnet/src/lib/Mocha.Core.UI.Server/Commands/ElementCommand.cs @@ -149,6 +149,11 @@ public class ElementCommand : InstanceCommand // POST http://localhost:4436/tenants/super/instances/2997$1/element // relatedInstance=4170$1 + if (oms.TryParseInstanceRef(context.Request.Form["relatedInstance"], out InstanceHandle h)) + { + omsContext.TargetInstance = h; + } + Response r = oms.ProcessElement(omsContext, elem, context.Request.Form); JsonObject obj = r.GetResponse(oms, omsContext); diff --git a/mocha-dotnet/src/lib/Mocha.Core/KnownInstanceGuids.cs b/mocha-dotnet/src/lib/Mocha.Core/KnownInstanceGuids.cs index 9556856..566ab1f 100755 --- a/mocha-dotnet/src/lib/Mocha.Core/KnownInstanceGuids.cs +++ b/mocha-dotnet/src/lib/Mocha.Core/KnownInstanceGuids.cs @@ -55,11 +55,14 @@ namespace Mocha.Core public static Guid ExtractSingleInstanceStringComponent { get; } = new Guid("{FCECCE4E-8D05-485A-AE34-B1B45E766661}"); public static Guid InstanceAttributeStringComponent { get; } = new Guid("{623565D5-5AEE-49ED-A5A9-0CFE670507BC}"); + public static Guid Prompt { get; } = new Guid("{EC889225-416A-4F73-B8D1-2A42B37AF43E}"); + /* public static Guid TextPrompt { get; } = new Guid("{195DDDD7-0B74-4498-BF61-B0549FE05CF3}"); public static Guid ChoicePrompt { get; } = new Guid("{5597AEEB-922D-48AF-AE67-DF7D951C71DB}"); public static Guid InstancePrompt { get; } = new Guid("{F3ECBF1E-E732-4370-BE05-8FA7CC520F50}"); public static Guid BooleanPrompt { get; } = new Guid("{a7b49c03-c9ce-4a79-a4b2-e94fc8cd8b29}"); + */ public static Guid Method { get; } = new Guid("{D2813913-80B6-4DD6-9AD6-56D989169734}"); diff --git a/mocha-dotnet/src/lib/Mocha.Core/Oms.cs b/mocha-dotnet/src/lib/Mocha.Core/Oms.cs index 820e580..d385890 100644 --- a/mocha-dotnet/src/lib/Mocha.Core/Oms.cs +++ b/mocha-dotnet/src/lib/Mocha.Core/Oms.cs @@ -18,6 +18,7 @@ namespace Mocha.Core; using System.Reflection; +using System.Runtime.Serialization; using System.Security.Cryptography; using System.Text; using System.Text.Json.Nodes; @@ -44,8 +45,12 @@ public abstract class Oms public bool ValidateConstraints { get; protected set; } = true; + public OmsStorage.OmsStorageCollection Storages { get; } + public Oms() { + Storages = new OmsStorage.OmsStorageCollection(this); + MethodImplementation[] methodImplementations = MBS.Core.Reflection.TypeLoader.GetAvailableTypes(new System.Reflection.Assembly[] { Assembly.GetExecutingAssembly() }); foreach (MethodImplementation impl in methodImplementations) { @@ -328,13 +333,22 @@ public abstract class Oms protected abstract void AssignRelationshipInternal(InstanceHandle source, InstanceHandle relationship, IEnumerable targets, DateTime effectiveDate); public void AssignRelationship(IInstanceReference source, InstanceHandle relationship, IEnumerable targets, DateTime? effectiveDate = null) { + DateTime dt = effectiveDate.GetValueOrDefault(DateTime.Now); + ValidateConstraintsForRelationship(source.GetHandle(), relationship, targets); - AssignRelationshipInternal(source.GetHandle(), relationship, targets, effectiveDate.GetValueOrDefault(DateTime.Now)); + AssignRelationshipInternal(source.GetHandle(), relationship, targets, dt); + + if (!_InhibitStorage) + { + foreach (OmsStorage sto in Storages) + { + sto.WriteRelationship(this, source.GetHandle(), relationship, targets, dt); + } + } } public void AssignRelationship(IInstanceReference source, Relationship relationship, IEnumerable targets, DateTime? effectiveDate = null) { - ValidateConstraintsForRelationship(source.GetHandle(), relationship.GetHandle(), targets); - AssignRelationshipInternal(source.GetHandle(), relationship.GetHandle(), targets, effectiveDate.GetValueOrDefault(DateTime.Now)); + AssignRelationship(source.GetHandle(), relationship.GetHandle(), targets, effectiveDate.GetValueOrDefault(DateTime.Now)); } public void AssignRelationship(IInstanceReference source, InstanceHandle relationship, IInstanceReference target, DateTime? effectiveDate = null) @@ -360,27 +374,75 @@ public abstract class Oms if (!ValidateConstraints) return; - if (TryGetAttributeValue(relationship, GetInstance(KnownAttributeGuids.Boolean.Singular), out bool value)) + // toggle switches for the various constraints + bool enforceSingularRelationshipConstraint = true; + bool validateSourceAndDestinationClasses = true; + + // this should possibly be moved outside of what's supposed to only be a "validate" routine + bool createSiblingRelationship = true; + + if (enforceSingularRelationshipConstraint) { - if (value) + if (TryGetAttributeValue(relationship, GetInstance(KnownAttributeGuids.Boolean.Singular), out bool value)) { - // enforce singular relationship constraint - if (targets.Count() > 1) + if (value) { - throw new InvalidOperationException("Relationship is Singular; must specify no more than one target instance"); + // enforce singular relationship constraint + if (targets.Count() > 1) + { + throw new InvalidOperationException("Relationship is Singular; must specify no more than one target instance"); + } } } } - - InstanceHandle siblingRelationship = GetRelatedInstance(relationship, GetInstance(KnownRelationshipGuids.Relationship__has_sibling__Relationship)); - if (siblingRelationship != InstanceHandle.Empty) + if (validateSourceAndDestinationClasses) { - DateTime dt = DateTime.Now; - foreach (InstanceHandle target in targets) + InstanceHandle ihSourceClass = GetRelatedInstance(relationship, GetInstance(KnownRelationshipGuids.Relationship__has_source__Class)); + InstanceHandle ihDestinationClass = GetRelatedInstance(relationship, GetInstance(KnownRelationshipGuids.Relationship__has_destination__Class)); + List ignoreClasses = new List(); + ignoreClasses.Add(GetInstance(KnownInstanceGuids.Classes.Instance)); + ignoreClasses.Add(GetInstance(KnownInstanceGuids.Classes.ExecutableReturningWorkData)); + + if (ihSourceClass != InstanceHandle.Empty) { - //* we use AssignRelationshipInternal here because calling the top-level function - //* would result in an infinite loop of sibling assignments - AssignRelationshipInternal(target, siblingRelationship, new InstanceHandle[] { source }, dt); + if (!IsInstanceOf(source, ihSourceClass) && !ignoreClasses.Contains(ihSourceClass)) + { + throw new InvalidOperationException(String.Format("Cannot assign relationship source class `{0}` to a type `{1}`", GetInstanceText(GetParentClass(source)), GetInstanceText(ihSourceClass))); + } + } + if (ihDestinationClass != InstanceHandle.Empty) + { + foreach (InstanceHandle target in targets) + { + if (!IsInstanceOf(target, ihDestinationClass) && !ignoreClasses.Contains(ihDestinationClass)) + { + throw new InvalidOperationException(String.Format("Cannot assign relationship destination class `{0}` to a type `{1}`", GetInstanceText(GetParentClass(target)), GetInstanceText(ihDestinationClass))); + } + } + } + } + if (createSiblingRelationship) + { + InstanceHandle siblingRelationship = GetRelatedInstance(relationship, GetInstance(KnownRelationshipGuids.Relationship__has_sibling__Relationship)); + if (siblingRelationship != InstanceHandle.Empty) + { + DateTime dt = DateTime.Now; + foreach (InstanceHandle target in targets) + { + //* we use AssignRelationshipInternal here because calling the top-level function + //* would result in an infinite loop of sibling assignments + AssignRelationshipInternal(target, siblingRelationship, new InstanceHandle[] { source }, dt); + + //? this also means that we need to manually loop through storages and update the siblings + //? if I had a nickel for every time I called AssignRelationshipInternal,... I'd have two nickels + if (!_InhibitStorage) + { + foreach (OmsStorage sto in Storages) + { + sto.WriteRelationship(this, target, siblingRelationship, new InstanceHandle[] { source }, dt); + } + } + } } } } @@ -389,6 +451,13 @@ public abstract class Oms public void ClearRelationship(InstanceHandle source, InstanceHandle relationship) { ClearRelationshipInternal(source, relationship); + if (!_InhibitStorage) + { + foreach (OmsStorage sto in Storages) + { + sto.ClearRelationship(this, source, relationship, DateTime.Now); + } + } } protected abstract bool TryGetInstanceInternal(Guid globalIdentifier, out InstanceHandle inst); @@ -527,12 +596,21 @@ public abstract class Oms } InstanceHandle ir = CreateInstance(guid); - SetParentClass(ir, ir_class.GetHandle()); int ct = CountInstances(ir_class.GetHandle()); InstanceKey pclass_key = GetInstanceKey(ir_class.GetHandle()); InstanceKey new_index = new InstanceKey(pclass_key.InstanceIndex, ct); SetInstanceKey(ir, new_index); + + DateTime dt = DateTime.Now; + if (!_InhibitStorage) + { + foreach (OmsStorage sto in Storages) + { + sto.CreateInstance(this, ir, ir_class.GetHandle(), dt); + } + } + SetParentClass(ir, ir_class.GetHandle()); return ir; } public T CreateInstanceOf(IInstanceReference ir_class, Guid guid) where T : ConcreteInstanceWrapper @@ -744,6 +822,7 @@ public abstract class Oms protected abstract void SetAttributeValueInternal(InstanceHandle source, InstanceHandle attribute, object value, DateTime effectiveDate); public void SetAttributeValue(IInstanceReference source, InstanceHandle attribute, object value, DateTime? effectiveDate = null) { + DateTime dt = effectiveDate.GetValueOrDefault(DateTime.Now); InstanceHandle sh = source.GetHandle(); ValidateConstraintsForAttribute(sh, attribute, value); @@ -756,7 +835,15 @@ public abstract class Oms _derivedData[sh][attribute] = value; return; } - SetAttributeValueInternal(sh, attribute, value, effectiveDate.GetValueOrDefault(DateTime.Now)); + SetAttributeValueInternal(sh, attribute, value, dt); + + if (!_InhibitStorage) + { + foreach (OmsStorage sto in Storages) + { + sto.WriteAttributeValue(this, source.GetHandle(), attribute, value, dt); + } + } } private void ValidateConstraintsForAttribute(InstanceHandle source, InstanceHandle attribute, object value) @@ -2450,4 +2537,15 @@ public abstract class Oms } return value; } + + private bool _InhibitStorage = false; + public void ReloadStorages() + { + _InhibitStorage = true; + foreach (OmsStorage sto in Storages) + { + sto.Load(this); + } + _InhibitStorage = false; + } } diff --git a/mocha-dotnet/src/lib/Mocha.Core/OmsMethodBuilder.cs b/mocha-dotnet/src/lib/Mocha.Core/OmsMethodBuilder.cs index 4c835b4..424fdca 100644 --- a/mocha-dotnet/src/lib/Mocha.Core/OmsMethodBuilder.cs +++ b/mocha-dotnet/src/lib/Mocha.Core/OmsMethodBuilder.cs @@ -352,7 +352,14 @@ public class OmsMethodBuilder { InstanceHandle method = CreateMethodBase(Oms.GetInstance(KnownInstanceGuids.MethodClasses.BuildUIResponseMethod), forClassInstance, verb, name, accessModifier, isStatic); InstanceHandle r_Build_UI_Response_Method__uses__Executable_returning_Element = Oms.GetInstance(KnownRelationshipGuids.Build_UI_Response_Method__uses__Executable_returning_Element); - Oms.AssignRelationship(method, r_Build_UI_Response_Method__uses__Executable_returning_Element, usesExecutableReturningElement); + if (usesExecutableReturningElement != InstanceHandle.Empty) + { + Oms.AssignRelationship(method, r_Build_UI_Response_Method__uses__Executable_returning_Element, usesExecutableReturningElement); + } + else + { + //? should we throw an exception here? + } return new BuildUIResponseMethod(method); } diff --git a/mocha-dotnet/src/lib/Mocha.Core/OmsStorage.cs b/mocha-dotnet/src/lib/Mocha.Core/OmsStorage.cs new file mode 100644 index 0000000..5a0cd7e --- /dev/null +++ b/mocha-dotnet/src/lib/Mocha.Core/OmsStorage.cs @@ -0,0 +1,87 @@ +// Copyright (C) 2025 Michael Becker +// +// This file is part of Mocha.NET. +// +// Mocha.NET is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Mocha.NET is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Mocha.NET. If not, see . + + +using System.Net.Sockets; +using System.Runtime.Serialization; + +namespace Mocha.Core; + +public abstract class OmsStorage +{ + public class OmsStorageCollection : System.Collections.ObjectModel.Collection + { + private Oms _parent; + public OmsStorageCollection(Oms parent) + { + _parent = parent; + } + } + + protected abstract void CreateInstanceInternal(Oms oms, InstanceHandle source, InstanceHandle parentClass, DateTime effectiveDate); + public void CreateInstance(Oms oms, InstanceHandle source, InstanceHandle parentClass, DateTime effectiveDate) + { + CreateInstanceInternal(oms, source, parentClass, effectiveDate); + if (AutoFlush) Flush(); + } + + protected abstract void WriteAttributeValueInternal(Oms oms, InstanceHandle source, InstanceHandle attribute, object value, DateTime effectiveDate); + public void WriteAttributeValue(Oms oms, InstanceHandle source, InstanceHandle attribute, object value, DateTime effectiveDate) + { + WriteAttributeValueInternal(oms, source, attribute, value, effectiveDate); + if (AutoFlush) Flush(); + } + + protected abstract void WriteRelationshipInternal(Oms oms, InstanceHandle source, InstanceHandle relationship, IEnumerable targets, DateTime effectiveDate); + public void WriteRelationship(Oms oms, InstanceHandle source, InstanceHandle relationship, IEnumerable targets, DateTime effectiveDate) + { + WriteRelationshipInternal(oms, source, relationship, targets, effectiveDate); + if (AutoFlush) Flush(); + } + + protected abstract void ClearRelationshipInternal(Oms oms, InstanceHandle source, InstanceHandle relationship, DateTime effectiveDate); + public void ClearRelationship(Oms oms, InstanceHandle source, InstanceHandle relationship, DateTime effectiveDate) + { + ClearRelationshipInternal(oms, source, relationship, effectiveDate); + if (AutoFlush) Flush(); + } + + protected abstract void OpenInternal(); + public void Open() + { + OpenInternal(); + } + + public bool AutoFlush { get; set; } = true; + + protected abstract void FlushInternal(); + public void Flush() + { + FlushInternal(); + } + protected abstract void CloseInternal(); + public void Close() + { + CloseInternal(); + } + + protected abstract void LoadInternal(Oms oms); + public void Load(Oms oms) + { + LoadInternal(oms); + } +} \ No newline at end of file diff --git a/mocha-dotnet/src/lib/Mocha.Core/Storage/BinaryOmsStorage.cs b/mocha-dotnet/src/lib/Mocha.Core/Storage/BinaryOmsStorage.cs new file mode 100644 index 0000000..4b7e6e5 --- /dev/null +++ b/mocha-dotnet/src/lib/Mocha.Core/Storage/BinaryOmsStorage.cs @@ -0,0 +1,138 @@ +// Copyright (C) 2025 Michael Becker +// +// This file is part of Mocha.NET. +// +// Mocha.NET is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Mocha.NET is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Mocha.NET. If not, see . + +using System.Data; +using System.Text.Json; +using System.Text.Json.Nodes; + +namespace Mocha.Core.Storage; + +public class BinaryOmsStorage : OmsStorage +{ + public Stream Stream { get; } + + public BinaryWriter Writer { get; } + + public BinaryOmsStorage(string path) + { + Stream = System.IO.File.Open(path, FileMode.Create, FileAccess.Write, FileShare.Read); + Writer = new BinaryWriter(Stream); + + Open(); + } + public BinaryOmsStorage(Stream stream) + { + Stream = stream; + Writer = new BinaryWriter(Stream); + + Open(); + } + + protected override void OpenInternal() + { + Writer.Write(new char[] { 'M', 'c', 'J', '!' }); + Writer.Write(1.0f); + } + + protected override void CreateInstanceInternal(Oms oms, InstanceHandle source, InstanceHandle parentClass, DateTime effectiveDate) + { + Guid sourceInstanceGuid = oms.GetGlobalIdentifier(source); + Guid classInstanceGuid = oms.GetGlobalIdentifier(parentClass); + + Writer.Write((byte)0xC1); + Writer.Write(sourceInstanceGuid.ToByteArray()); + Writer.Write(classInstanceGuid.ToByteArray()); + + InstanceKey ik = oms.GetInstanceKey(source); + Writer.Write(ik.ClassIndex); + Writer.Write(ik.InstanceIndex); + + Writer.Write(effectiveDate.ToBinary()); + Writer.Write(DateTime.Now.ToBinary()); + } + + protected override void WriteAttributeValueInternal(Oms oms, InstanceHandle source, InstanceHandle attribute, object value, DateTime effectiveDate) + { + Guid sourceInstanceGuid = oms.GetGlobalIdentifier(source); + Guid attributeInstanceGuid = oms.GetGlobalIdentifier(attribute); + + Writer.Write((byte)0xA1); + Writer.Write(sourceInstanceGuid.ToByteArray()); + Writer.Write(attributeInstanceGuid.ToByteArray()); + WriteBinaryValue(Writer, value); + + Writer.Write(effectiveDate.ToBinary()); + Writer.Write(DateTime.Now.ToBinary()); + } + + private void WriteBinaryValue(BinaryWriter writer, object value) + { + if (value is string s) + { + writer.Write((int)1); + writer.Write(s); + } + } + + protected override void WriteRelationshipInternal(Oms oms, InstanceHandle source, InstanceHandle relationship, IEnumerable targets, DateTime effectiveDate) + { + Guid sourceInstanceGuid = oms.GetGlobalIdentifier(source); + Guid relationshipInstanceGuid = oms.GetGlobalIdentifier(relationship); + + Writer.Write((byte)0xB1); + Writer.Write(sourceInstanceGuid.ToByteArray()); + Writer.Write(relationshipInstanceGuid.ToByteArray()); + + Writer.Write((int)targets.Count()); + foreach (InstanceHandle target in targets) + { + Writer.Write(oms.GetGlobalIdentifier(target).ToByteArray()); + } + + Writer.Write(effectiveDate.ToBinary()); + Writer.Write(DateTime.Now.ToBinary()); + } + + protected override void ClearRelationshipInternal(Oms oms, InstanceHandle source, InstanceHandle relationship, DateTime effectiveDate) + { + Guid sourceInstanceGuid = oms.GetGlobalIdentifier(source); + Guid relationshipInstanceGuid = oms.GetGlobalIdentifier(relationship); + + Writer.Write((byte)0xB0); + Writer.Write(sourceInstanceGuid.ToByteArray()); + Writer.Write(relationshipInstanceGuid.ToByteArray()); + + Writer.Write(effectiveDate.ToBinary()); + Writer.Write(DateTime.Now.ToBinary()); + } + + protected override void FlushInternal() + { + Writer.Flush(); + } + + protected override void CloseInternal() + { + Writer.Flush(); + Stream.Close(); + } + + protected override void LoadInternal(Oms oms) + { + } + +} \ No newline at end of file diff --git a/mocha-dotnet/src/lib/Mocha.Core/Storage/JsonOmsStorage.cs b/mocha-dotnet/src/lib/Mocha.Core/Storage/JsonOmsStorage.cs new file mode 100644 index 0000000..aa8f52c --- /dev/null +++ b/mocha-dotnet/src/lib/Mocha.Core/Storage/JsonOmsStorage.cs @@ -0,0 +1,147 @@ +// Copyright (C) 2025 Michael Becker +// +// This file is part of Mocha.NET. +// +// Mocha.NET is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Mocha.NET is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Mocha.NET. If not, see . + +using System.Text.Json; +using System.Text.Json.Nodes; +using Mocha.Core.Oop.Methods; + +namespace Mocha.Core.Storage; + +public class JsonOmsStorage : StreamOmsStorage +{ + public JsonOmsStorage(string path) : base(path) { } + public JsonOmsStorage(Stream stream) : base(stream) { } + + private JsonArray PrimaryArray { get; } = new JsonArray(); + + protected override void OpenInternal() + { + } + + protected override void CreateInstanceInternal(Oms oms, InstanceHandle source, InstanceHandle parentClass, DateTime effectiveDate) + { + Guid sourceInstanceGuid = oms.GetGlobalIdentifier(source); + Guid classInstanceGuid = oms.GetGlobalIdentifier(parentClass); // calling oms.GetParentClass(source) here is unreliable + + JsonObject obj = new JsonObject(); + obj.Add("type", "createInstance"); + obj.Add("globalIdentifier", sourceInstanceGuid.ToString("B")); + obj.Add("parentClassGlobalIdentifier", classInstanceGuid.ToString("B")); + obj.Add("iid", oms.GetInstanceKey(source).ToString()); + obj.Add("edate", effectiveDate); + obj.Add("cdate", DateTime.Now); + + PrimaryArray.Add(obj); + } + + protected override void WriteAttributeValueInternal(Oms oms, InstanceHandle source, InstanceHandle attribute, object value, DateTime effectiveDate) + { + Guid sourceInstanceGuid = oms.GetGlobalIdentifier(source); + Guid attributeInstanceGuid = oms.GetGlobalIdentifier(attribute); + + JsonObject obj = new JsonObject(); + obj.Add("type", "assignAttribute"); + obj.Add("src", sourceInstanceGuid.ToString("B")); + obj.Add("att", attributeInstanceGuid.ToString("B")); + obj.Add("value", JsonValue.Create(value)); + obj.Add("edate", effectiveDate); + obj.Add("cdate", DateTime.Now); + + PrimaryArray.Add(obj); + } + + protected override void WriteRelationshipInternal(Oms oms, InstanceHandle source, InstanceHandle relationship, IEnumerable targets, DateTime effectiveDate) + { + Guid sourceInstanceGuid = oms.GetGlobalIdentifier(source); + Guid relationshipInstanceGuid = oms.GetGlobalIdentifier(relationship); + + JsonObject obj = new JsonObject(); + obj.Add("type", "assignRelationship"); + obj.Add("src", sourceInstanceGuid.ToString("B")); + obj.Add("rel", relationshipInstanceGuid.ToString("B")); + + JsonArray ary = new JsonArray(); + foreach (InstanceHandle target in targets) + { + ary.Add(oms.GetGlobalIdentifier(target).ToString("B")); + } + + obj.Add("tgts", ary); + obj.Add("edate", effectiveDate); + obj.Add("cdate", DateTime.Now); + PrimaryArray.Add(obj); + } + + protected override void ClearRelationshipInternal(Oms oms, InstanceHandle source, InstanceHandle relationship, DateTime effectiveDate) + { + Guid sourceInstanceGuid = oms.GetGlobalIdentifier(source); + Guid relationshipInstanceGuid = oms.GetGlobalIdentifier(relationship); + + JsonObject obj = new JsonObject(); + obj.Add("type", "clearRelationship"); + obj.Add("src", sourceInstanceGuid.ToString("B")); + obj.Add("rel", relationshipInstanceGuid.ToString("B")); + obj.Add("edate", effectiveDate); + obj.Add("cdate", DateTime.Now); + PrimaryArray.Add(obj); + } + + protected override void CloseInternal() + { + JsonObject obj = new JsonObject(); + obj.Add("blocks", PrimaryArray); + + StreamWriter sw = new StreamWriter(Stream); + sw.Write(obj.ToString()); + sw.Flush(); + + base.CloseInternal(); + } + + protected override void LoadInternal(Oms oms) + { + StreamReader sr = new StreamReader(Stream); + string text = sr.ReadToEnd(); + + JsonObject? obj = (JsonObject?)JsonNode.Parse(text); + if (obj != null) + { + if (obj.ContainsKey("blocks")) + { + foreach (JsonObject obj2 in obj["blocks"].AsArray()) + { + string type = obj2["type"].AsValue().ToString(); + if (type == "createInstance") + { + Guid globalIdentifier = Guid.Parse(obj2["globalIdentifier"].ToString()); + Guid parentClassGlobalIdentifier = Guid.Parse(obj2["parentClassGlobalIdentifier"].ToString()); + InstanceKey iid = InstanceKey.Parse(obj2["iid"].ToString()); + + InstanceHandle ih = oms.CreateInstanceOf(oms.GetInstance(parentClassGlobalIdentifier), globalIdentifier); + oms.SetInstanceKey(ih, iid); + } + else if (type == "assignAttribute") + { + + } + } + } + + } + + } +} \ No newline at end of file diff --git a/mocha-dotnet/src/lib/Mocha.Core/Storage/StreamOmsStorage.cs b/mocha-dotnet/src/lib/Mocha.Core/Storage/StreamOmsStorage.cs new file mode 100644 index 0000000..4bb6530 --- /dev/null +++ b/mocha-dotnet/src/lib/Mocha.Core/Storage/StreamOmsStorage.cs @@ -0,0 +1,40 @@ +// Copyright (C) 2025 Michael Becker +// +// This file is part of Mocha.NET. +// +// Mocha.NET is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Mocha.NET is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Mocha.NET. If not, see . + + +namespace Mocha.Core.Storage; + +public abstract class StreamOmsStorage : OmsStorage +{ + public Stream Stream { get; } + + public StreamOmsStorage(string path) : this(System.IO.File.Open(path, FileMode.Create, FileAccess.ReadWrite, FileShare.Read)) { } + public StreamOmsStorage(Stream stream) + { + Stream = stream; + } + + protected override void FlushInternal() + { + Stream.Flush(); + } + protected override void CloseInternal() + { + Stream.Close(); + } + +} \ No newline at end of file diff --git a/mocha-dotnet/src/lib/Mocha.Testing/OmsTestsBase.cs b/mocha-dotnet/src/lib/Mocha.Testing/OmsTestsBase.cs index 24fe1c1..c1de852 100644 --- a/mocha-dotnet/src/lib/Mocha.Testing/OmsTestsBase.cs +++ b/mocha-dotnet/src/lib/Mocha.Testing/OmsTestsBase.cs @@ -53,12 +53,21 @@ public abstract class OmsTestsBase return oms; } + public void ReloadOms() + { + Oms = CreateOms(); + } + protected readonly Guid TEST_CLASS_GUID = new Guid("{5821fc28-6411-4339-a7d2-56dc05591501}"); protected readonly Guid TEST_CLASS2_GUID = new Guid("{6351ecb7-da5d-4029-ba3a-196a6dc980e9}"); protected readonly Guid TEST_CLASS_INST_GUID = new Guid("{12b4bd54-dabd-4074-be50-ffd78115a85a}"); protected readonly Guid TEST_ATTR_GUID = new Guid("{e4f314a4-f4b1-4cf1-95d8-7a927fb7c8a0}"); protected readonly Guid TEST_ATTR_BOOL_GUID = new Guid("{b8dc887c-9964-4a66-94b1-6dcd00b196dc}"); protected readonly Guid TEST_ATTR_NUM_GUID = new Guid("{62d633df-47ef-43b7-a108-b0b47f5212f6}"); + /// + /// Test Class.has Test Class 2 + /// + /// protected readonly Guid TEST_REL_GUID = new Guid("{969d98bc-95e0-45a9-92e5-d70c62980c0e}"); protected readonly Guid TEST_REL2_GUID = new Guid("{235f025a-90a6-43a6-9be1-0b32716eb95a}"); protected readonly string TEST_ATTR_VALUE = "The quick brown fox jumps over the lazy dog."; @@ -66,7 +75,7 @@ public abstract class OmsTestsBase [SetUp] public void Setup() { - Oms = CreateOms(); + ReloadOms(); // create the Test Class InstanceHandle c_Class = Oms.GetInstance(KnownInstanceGuids.Classes.Class); diff --git a/mocha-dotnet/tests/Mocha.Core.Tests/BasicTests.cs b/mocha-dotnet/tests/Mocha.Core.Tests/BasicTests.cs index 4c1511a..1b60086 100644 --- a/mocha-dotnet/tests/Mocha.Core.Tests/BasicTests.cs +++ b/mocha-dotnet/tests/Mocha.Core.Tests/BasicTests.cs @@ -74,13 +74,16 @@ public class BasicTests : OmsTestsBase InstanceHandle irTestClass2 = Oms.GetInstance(TEST_CLASS2_GUID); Assert.That(irTestClass2, Is.Not.EqualTo(InstanceHandle.Empty)); + InstanceHandle ihTestClassInstance = Oms.CreateInstanceOf(irTestClass); + InstanceHandle ihTestClassInstance2 = Oms.CreateInstanceOf(irTestClass2); + InstanceHandle irTestRelationship = Oms.GetInstance(TEST_REL_GUID); Assert.That(irTestRelationship, Is.Not.EqualTo(InstanceHandle.Empty)); - Oms.AssignRelationship(irTestClass, irTestRelationship, irTestClass2); + Oms.AssignRelationship(ihTestClassInstance, irTestRelationship, ihTestClassInstance2); - InstanceHandle irRelated = Oms.GetRelatedInstance(irTestClass, irTestRelationship); - Assert.That(irRelated, Is.EqualTo(irTestClass2)); + InstanceHandle irRelated = Oms.GetRelatedInstance(ihTestClassInstance, irTestRelationship); + Assert.That(irRelated, Is.EqualTo(ihTestClassInstance2)); } [Test] diff --git a/mocha-dotnet/tests/Mocha.Core.Tests/MethodTests/EvaluateBooleanExpressionMethodTests.cs b/mocha-dotnet/tests/Mocha.Core.Tests/MethodTests/EvaluateBooleanExpressionMethodTests.cs index 6b6175f..e46253e 100644 --- a/mocha-dotnet/tests/Mocha.Core.Tests/MethodTests/EvaluateBooleanExpressionMethodTests.cs +++ b/mocha-dotnet/tests/Mocha.Core.Tests/MethodTests/EvaluateBooleanExpressionMethodTests.cs @@ -61,7 +61,7 @@ public class EvaluateBooleanExpressionMethodTests : MethodTestsBase EvaluateBooleanExpressionMethod Method__is__Conditional_Select_Attribute_Method = methodBuilder.CreateEvaluateBooleanExpressionMethod(c_Method, "is", "SAC", AccessModifier.Public, false, Oms.GetInstance(KnownAttributeGuids.Boolean.MethodIsOfTypeSpecified), Instance__get__Parent_Class_rsmb.GetHandle(), RelationalOperator.InSelectionList, Oms.GetInstance(KnownInstanceGuids.MethodClasses.ConditionalSelectAttributeMethod)); ReturnAttributeMethodBinding Method__is__Conditional_Select_Attribute_Method_ramb = Method__is__Conditional_Select_Attribute_Method.CreateMethodBinding(Oms); - + EvaluateBooleanExpressionMethod Method__is__Get_Relationship_Method = methodBuilder.CreateEvaluateBooleanExpressionMethod(c_Method, "is", "GR", AccessModifier.Public, false, Oms.GetInstance(KnownAttributeGuids.Boolean.MethodIsOfTypeSpecified), Instance__get__Parent_Class_rsmb.GetHandle(), RelationalOperator.InSelectionList, Oms.GetInstance(KnownInstanceGuids.MethodClasses.GetRelationshipMethod)); ReturnAttributeMethodBinding Method__is__Get_Relationship_Method_ramb = Method__is__Get_Relationship_Method.CreateMethodBinding(Oms); diff --git a/mocha-dotnet/tests/Mocha.Core.Tests/RelationshipTests.cs b/mocha-dotnet/tests/Mocha.Core.Tests/RelationshipTests.cs index 6f995c0..8cfca86 100644 --- a/mocha-dotnet/tests/Mocha.Core.Tests/RelationshipTests.cs +++ b/mocha-dotnet/tests/Mocha.Core.Tests/RelationshipTests.cs @@ -37,15 +37,15 @@ public class RelationshipTests : OmsTestsBase public void SetUp() { InstanceHandle c_Class = Oms.GetInstance(KnownInstanceGuids.Classes.Class); - + c_TestClass = Oms.GetInstance(TEST_CLASS_GUID); c_TestClass2 = Oms.GetInstance(TEST_CLASS2_GUID); - + c_TestClassInstance = Oms.CreateInstanceOf(c_TestClass); - + c_TestClass2Instance1 = Oms.CreateInstanceOf(c_TestClass2); c_TestClass2Instance2 = Oms.CreateInstanceOf(c_TestClass2); - + r_Test_Class__has_single__Test_Class_2 = Oms.CreateRelationship(c_TestClass, "has single", c_TestClass2, TEST_REL3_GUID, true, "single for", TEST_REL4_GUID); r_Test_Class_2__single_for__Test_Class = Oms.GetInstance(TEST_REL4_GUID); @@ -86,4 +86,30 @@ public class RelationshipTests : OmsTestsBase InstanceHandle ir = Oms.GetRelatedInstance(c_TestClass2Instance1, r_Test_Class_2__single_for__Test_Class); Assert.That(ir, Is.EqualTo(c_TestClassInstance)); } + + [Test] + public void CompatibleRelationshipAssignmentThrowsNothing() + { + InstanceHandle ihClass = Oms.GetInstance(KnownInstanceGuids.Classes.Class); + InstanceHandle ihSubClass = Oms.CreateInstanceOf(Oms.GetInstance(KnownInstanceGuids.Classes.BooleanAttribute)); + InstanceHandle ihRel = Oms.GetInstance(KnownRelationshipGuids.Class__has__Attribute); + + Assert.That(delegate () + { + Oms.AssignRelationship(ihClass, ihRel, ihSubClass); + }, Throws.Nothing); + } + + [Test] + public void IncompatibleRelationshipAssignmentThrowsException() + { + InstanceHandle ihClass = Oms.GetInstance(KnownInstanceGuids.Classes.Class); + InstanceHandle ihSubClass = Oms.GetInstance(KnownInstanceGuids.Classes.BooleanAttribute); + InstanceHandle ihRel = Oms.GetInstance(KnownRelationshipGuids.Class__valid_for__Work_Set); + + Assert.That(delegate () + { + Oms.AssignRelationship(ihClass, ihRel, ihSubClass); + }, Throws.InvalidOperationException); + } } \ No newline at end of file diff --git a/mocha-dotnet/tests/Mocha.Core.Tests/Storage/JsonStorageTests.cs b/mocha-dotnet/tests/Mocha.Core.Tests/Storage/JsonStorageTests.cs new file mode 100644 index 0000000..3b4f067 --- /dev/null +++ b/mocha-dotnet/tests/Mocha.Core.Tests/Storage/JsonStorageTests.cs @@ -0,0 +1,70 @@ +// Copyright (C) 2025 Michael Becker +// +// This file is part of Mocha.NET. +// +// Mocha.NET is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Mocha.NET is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Mocha.NET. If not, see . + +using Mocha.Core.Storage; +using Mocha.Testing; + +namespace Mocha.Core.Tests.Storage; + +public class JsonStorageTests : OmsTestsBase +{ + [Test(Author = "Michael Becker", Description = "Verifies that the create instance, assign attribute, and assign relationship operations save to and load from storage.")] + public void BasicTests() + { + MemoryStream ms = new MemoryStream(); + JsonOmsStorage json = new JsonOmsStorage(ms); + Oms.Storages.Add(json); + + InstanceHandle ihTestClass = Oms.GetInstance(TEST_CLASS_GUID); + InstanceHandle ihTestClass2 = Oms.GetInstance(TEST_CLASS2_GUID); + InstanceHandle ihRel = Oms.GetInstance(TEST_REL_GUID); + + InstanceHandle test = Oms.CreateInstanceOf(ihTestClass, new Guid("1e9b7101-13dc-4d97-9ae4-570a07bd3545")); + InstanceHandle test2 = Oms.CreateInstanceOf(ihTestClass2, new Guid("eea0defa-e93d-4b7e-bbe5-c3d654791335")); + + Oms.SetAttributeValue(test, Oms.GetInstance(KnownAttributeGuids.Text.Name), "PICADILLY"); + + // check that it worked + string v = Oms.GetAttributeValue(test, Oms.GetInstance(KnownAttributeGuids.Text.Name)); + Assert.That(v, Is.EqualTo("PICADILLY")); + + Oms.AssignRelationship(test, ihRel, test2); + + json.Close(); + + byte[] data = ms.ToArray(); + System.IO.File.WriteAllBytes("/tmp/test.json", data); + + // reload the OMS from scratch + ReloadOms(); + + ms = new MemoryStream(data); + json = new JsonOmsStorage(ms); + + // don't load the save data yet, check to make sure nothing's changed on the OMS + Oms.TryGetInstance(new Guid("1e9b7101-13dc-4d97-9ae4-570a07bd3545"), out test); + Assert.That(test, Is.EqualTo(InstanceHandle.Empty)); + + // now, load the save data + Oms.Storages.Add(json); + Oms.ReloadStorages(); + + // and see if we have the newly-created instance + Oms.TryGetInstance(new Guid("1e9b7101-13dc-4d97-9ae4-570a07bd3545"), out test); + Assert.That(test, Is.Not.EqualTo(InstanceHandle.Empty)); + } +} \ No newline at end of file