fix and improve persistence / storage functionality complete with unit tests

This commit is contained in:
Michael Becker 2025-09-30 08:41:35 -04:00
parent 0c7abc9a13
commit 39cdecd9f4
13 changed files with 840 additions and 372 deletions

View File

@ -18,7 +18,6 @@
namespace Mocha.Core;
using System.Reflection;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json.Nodes;
@ -340,11 +339,16 @@ public abstract class Oms
ValidateConstraintsForRelationship(source.GetHandle(), relationship, targets);
AssignRelationshipInternal(source.GetHandle(), relationship, targets, dt);
if (!_InhibitStorage)
if (!InhibitStorageProcessing)
{
List<InstanceReference> list = new List<InstanceReference>();
foreach (InstanceHandle ih in targets)
{
list.Add(new InstanceReference(GetGlobalIdentifier(ih)));
}
foreach (OmsStorage sto in Storages)
{
sto.WriteRelationship(this, source.GetHandle(), relationship, targets, dt);
sto.WriteRelationship(new InstanceReference(GetGlobalIdentifier(source)), new InstanceReference(GetGlobalIdentifier(relationship)), list, dt, false);
}
}
}
@ -447,11 +451,11 @@ public abstract class Oms
//? 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)
if (!InhibitStorageProcessing)
{
foreach (OmsStorage sto in Storages)
{
sto.WriteRelationship(this, target, siblingRelationship, new InstanceHandle[] { source }, dt);
sto.WriteRelationship(new InstanceReference(GetGlobalIdentifier(target)), new InstanceReference(GetGlobalIdentifier(siblingRelationship)), new InstanceReference[] { new InstanceReference(GetGlobalIdentifier(source)) }, dt, false);
}
}
}
@ -463,11 +467,11 @@ public abstract class Oms
public void ClearRelationship(InstanceHandle source, InstanceHandle relationship)
{
ClearRelationshipInternal(source, relationship);
if (!_InhibitStorage)
if (!InhibitStorageProcessing)
{
foreach (OmsStorage sto in Storages)
{
sto.ClearRelationship(this, source, relationship, DateTime.Now);
sto.ClearRelationship(new InstanceReference(GetGlobalIdentifier(source)), new InstanceReference(GetGlobalIdentifier(relationship)), DateTime.Now);
}
}
}
@ -492,6 +496,15 @@ public abstract class Oms
throw new KeyNotFoundException(String.Format("inst not found: {0}", globalIdentifier));
}
public InstanceHandle GetInstance(InstanceReference ir)
{
if (ir.GlobalIdentifier != Guid.Empty)
{
return GetInstance(ir.GlobalIdentifier);
}
return GetInstance(ir.InstanceKey);
}
private Dictionary<string, InstanceHandle> _derivedInstances = new Dictionary<string, InstanceHandle>();
public InstanceHandle GetInstance(InstanceKey ik)
{
@ -620,11 +633,11 @@ public abstract class Oms
*/
DateTime dt = DateTime.Now;
if (!_InhibitStorage)
if (!InhibitStorageProcessing)
{
foreach (OmsStorage sto in Storages)
{
sto.CreateInstance(this, ir, ir_class.GetHandle(), dt);
sto.CreateInstance(new InstanceReference(guid), dt);
}
}
SetParentClass(ir, ir_class.GetHandle());
@ -856,11 +869,11 @@ public abstract class Oms
Log.WriteLine("oms: setting attribute value {0} . {1} = '{2}'", sh, attribute, value);
SetAttributeValueInternal(sh, attribute, value, dt);
if (!_InhibitStorage)
if (!InhibitStorageProcessing)
{
foreach (OmsStorage sto in Storages)
{
sto.WriteAttributeValue(this, source.GetHandle(), attribute, value, dt);
sto.WriteAttributeValue(new InstanceReference(GetGlobalIdentifier(source.GetHandle())), new InstanceReference(GetGlobalIdentifier(attribute)), value, dt);
}
}
}
@ -2605,14 +2618,28 @@ public abstract class Oms
return value;
}
private bool _InhibitStorage = false;
internal bool InhibitStorageProcessing { get; set; } = false;
public void ReloadStorages()
{
_InhibitStorage = true;
InhibitStorageProcessing = true;
foreach (OmsStorage sto in Storages)
{
sto.Load(this);
}
_InhibitStorage = false;
InhibitStorageProcessing = false;
}
internal InstanceHandle UnsafeCreateInstance(InstanceReference ir)
{
return UnsafeCreateInstance(ir.GlobalIdentifier, ir.InstanceKey);
}
internal InstanceHandle UnsafeCreateInstance(Guid guid, InstanceKey ik)
{
InstanceHandle ih = CreateInstance(guid);
if (ik != InstanceKey.Empty)
{
SetInstanceKey(ih, ik);
}
return ih;
}
}

View File

@ -1,4 +1,4 @@
// Copyright (C) 2025 Michael Becker <alcexhim@gmail.com>
// Copyright (C) 2024 Michael Becker <alcexhim@gmail.com>
//
// This file is part of Mocha.NET.
//
@ -15,73 +15,108 @@
// You should have received a copy of the GNU General Public License
// along with Mocha.NET. If not, see <https://www.gnu.org/licenses/>.
using System.Net.Sockets;
using System.Runtime.Serialization;
namespace Mocha.Core;
public abstract class OmsStorage
{
public class OmsStorageCollection : System.Collections.ObjectModel.Collection<OmsStorage>
{
private Oms _parent;
public OmsStorageCollection(Oms parent)
public Oms Oms { get; private set; }
public OmsStorageCollection(Oms oms)
{
_parent = parent;
Oms = oms;
}
}
protected abstract void CreateInstanceInternal(Oms oms, InstanceHandle source, InstanceHandle parentClass, DateTime effectiveDate);
public void CreateInstance(Oms oms, InstanceHandle source, InstanceHandle parentClass, DateTime effectiveDate)
private bool _ReadOnly = false;
public bool ReadOnly { get { return _ReadOnly; } }
protected OmsStorage(bool readOnly)
{
CreateInstanceInternal(oms, source, parentClass, effectiveDate);
if (AutoFlush) Flush();
_ReadOnly = readOnly;
}
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<InstanceHandle> targets, DateTime effectiveDate);
public void WriteRelationship(Oms oms, InstanceHandle source, InstanceHandle relationship, IEnumerable<InstanceHandle> 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();
protected virtual void CloseInternal() { }
public void Close()
{
CloseInternal();
}
protected virtual void ResolveDependenciesInternal(Oms oms)
{
}
internal void ResolveDependencies(Oms oms)
{
ResolveDependenciesInternal(oms);
}
protected abstract void CreateInstanceInternal(InstanceReference instance, DateTime effectiveDate);
public void CreateInstance(InstanceReference instance, DateTime effectiveDate)
{
if (ReadOnly)
return;
CreateInstanceInternal(instance, effectiveDate);
if (AutoFlush)
{
Flush();
}
}
protected abstract void WriteAttributeValueInternal(InstanceReference sourceInstance, InstanceReference attributeInstance, object value, DateTime effectiveDate);
public void WriteAttributeValue(InstanceReference sourceInstance, InstanceReference attributeInstance, object value, DateTime effectiveDate)
{
if (ReadOnly)
return;
WriteAttributeValueInternal(sourceInstance, attributeInstance, value, effectiveDate);
if (AutoFlush)
{
Flush();
}
}
protected abstract void ClearRelationshipInternal(InstanceReference sourceInstance, InstanceReference relationshipInstance, DateTime effectiveDate);
public void ClearRelationship(InstanceReference sourceInstance, InstanceReference relationshipInstance, DateTime effectiveDate)
{
if (ReadOnly)
return;
ClearRelationshipInternal(sourceInstance, relationshipInstance, effectiveDate);
if (AutoFlush)
{
Flush();
}
}
protected abstract void WriteRelationshipInternal(InstanceReference sourceInstance, InstanceReference attributeInstance, IEnumerable<InstanceReference> targetInstances, DateTime effectiveDate, bool remove);
public void WriteRelationship(InstanceReference sourceInstance, InstanceReference attributeInstance, IEnumerable<InstanceReference> targetInstances, DateTime effectiveDate, bool remove)
{
if (ReadOnly)
return;
WriteRelationshipInternal(sourceInstance, attributeInstance, targetInstances, effectiveDate, remove);
if (AutoFlush)
{
Flush();
}
}
protected abstract void LoadInternal(Oms oms);
public void Load(Oms oms)
{
oms.InhibitStorageProcessing = true;
LoadInternal(oms);
oms.InhibitStorageProcessing = false;
}
}
public bool AutoFlush { get; set; } = true;
protected virtual void FlushInternal()
{
}
public void Flush()
{
FlushInternal();
}
}

View File

@ -0,0 +1,214 @@
// Copyright (C) 2025 Michael Becker <alcexhim@gmail.com>
//
// 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 <https://www.gnu.org/licenses/>.
namespace Mocha.Core.Storage.Binary;
public class BinaryOmsStorage : OmsStorage
{
private Stream _Stream;
public Stream Stream { get { return _Stream; } }
private BinaryWriter _Writer;
private BinaryWriter Writer { get { return _Writer; } }
private BinaryReader _Reader;
private BinaryReader Reader { get { return _Reader; } }
public BinaryOmsStorage(Stream stream, bool readOnly = false) : base(readOnly)
{
_Stream = stream;
if (!readOnly)
{
_Writer = new BinaryWriter(stream);
}
_Reader = new BinaryReader(stream);
}
private void WriteData(BinaryWriter writer, object value)
{
if (value is string)
{
writer.Write((int)BinaryOmsStorageAttributeType.Text);
writer.Write((string)value);
return;
}
else if (value is bool)
{
writer.Write((int)BinaryOmsStorageAttributeType.Boolean);
writer.Write((bool)value);
return;
}
else if (value is decimal)
{
writer.Write((int)BinaryOmsStorageAttributeType.Numeric);
writer.Write((decimal)value);
return;
}
else if (value is DateTime)
{
writer.Write((int)BinaryOmsStorageAttributeType.Date);
writer.Write(((DateTime)value).ToBinary());
return;
}
throw new InvalidOperationException();
}
private object ReadData(BinaryReader reader)
{
BinaryOmsStorageAttributeType type = (BinaryOmsStorageAttributeType)reader.ReadInt32();
if (type == BinaryOmsStorageAttributeType.Text)
{
return reader.ReadString();
}
else if (type == BinaryOmsStorageAttributeType.Boolean)
{
return reader.ReadBoolean();
}
else if (type == BinaryOmsStorageAttributeType.Numeric)
{
return reader.ReadDecimal();
}
else if (type == BinaryOmsStorageAttributeType.Date)
{
return DateTime.FromBinary(reader.ReadInt64());
}
throw new InvalidOperationException();
}
protected override void CreateInstanceInternal(InstanceReference sourceInstance, DateTime effectiveDate)
{
Writer.Write((byte)BinaryOmsStorageOpCode.CreateInstance);
WriteInstance(sourceInstance);
Writer.Write(effectiveDate.ToBinary());
}
protected override void WriteAttributeValueInternal(InstanceReference sourceInstance, InstanceReference attributeInstance, object value, DateTime effectiveDate)
{
Writer.Write((byte)BinaryOmsStorageOpCode.AssignAttribute);
WriteInstance(sourceInstance);
WriteInstance(attributeInstance);
Writer.Write(effectiveDate.ToBinary());
WriteData(Writer, value);
}
private void WriteInstance(InstanceReference instanceReference)
{
WriteInstanceKey(instanceReference.InstanceKey);
WriteGuid(instanceReference.GlobalIdentifier);
}
protected override void ClearRelationshipInternal(InstanceReference sourceInstance, InstanceReference relationshipInstance, DateTime effectiveDate)
{
_Writer.Write((byte)BinaryOmsStorageOpCode.ClearRelationship);
WriteInstance(sourceInstance);
WriteInstance(relationshipInstance);
Writer.Write(effectiveDate.ToBinary());
}
protected override void WriteRelationshipInternal(InstanceReference sourceInstance, InstanceReference relationshipInstance, IEnumerable<InstanceReference> targetInstances, DateTime effectiveDate, bool remove)
{
_Writer.Write((byte)BinaryOmsStorageOpCode.AssignRelationship);
WriteInstance(sourceInstance);
WriteInstance(relationshipInstance);
Writer.Write(effectiveDate.ToBinary());
_Writer.Write(targetInstances.Count());
foreach (InstanceReference ir in targetInstances)
{
WriteInstance(ir);
}
}
private void WriteInstanceKey(InstanceKey instanceKey)
{
Writer.Write(instanceKey.ClassIndex);
Writer.Write(instanceKey.InstanceIndex);
}
private void WriteGuid(Guid guid)
{
Writer.Write(guid.ToByteArray());
}
protected InstanceKey ReadInstanceKey()
{
int classIndex = Reader.ReadInt32();
int instanceIndex = Reader.ReadInt32();
return new InstanceKey(classIndex, instanceIndex);
}
protected Guid ReadGuid()
{
byte[] data = Reader.ReadBytes(16);
return new Guid(data);
}
protected virtual void ApplyOperation(Oms oms, BinaryOmsStorageOpCode opcode)
{
if (opcode == BinaryOmsStorageOpCode.CreateInstance)
{
throw new NotImplementedException();
}
else if (opcode == BinaryOmsStorageOpCode.AssignAttribute)
{
InstanceReference sourceInstance = ReadInstance();
InstanceReference attributeInstance = ReadInstance();
DateTime effectiveDate = DateTime.FromBinary(Reader.ReadInt64());
object value = ReadData(Reader);
oms.SetAttributeValue(sourceInstance.GetHandle(oms), attributeInstance.GetHandle(oms), value, effectiveDate);
}
else if (opcode == BinaryOmsStorageOpCode.AssignRelationship)
{
InstanceReference sourceInstance = ReadInstance();
InstanceReference relationshipInstance = ReadInstance();
DateTime effectiveDate = DateTime.FromBinary(Reader.ReadInt64());
int instanceCount = Reader.ReadInt32();
InstanceReference[] iks = new InstanceReference[instanceCount];
for (int i = 0; i < instanceCount; i++)
{
InstanceReference ik = ReadInstance();
iks[i] = ik;
}
oms.AssignRelationship(sourceInstance.GetHandle(oms), relationshipInstance.GetHandle(oms), InstanceReference.GetHandles(iks, oms), effectiveDate);
}
}
protected override void LoadInternal(Oms oms)
{
while (true)
{
try
{
BinaryOmsStorageOpCode opcode = (BinaryOmsStorageOpCode)Reader.ReadByte();
if (opcode == BinaryOmsStorageOpCode.Invalid) return;
ApplyOperation(oms, opcode);
}
catch (EndOfStreamException ex)
{
break;
}
}
}
protected InstanceReference ReadInstance()
{
InstanceKey sourceInstanceKey = ReadInstanceKey();
Guid sourceInstanceGuid = ReadGuid();
return new InstanceReference(sourceInstanceKey, sourceInstanceGuid);
}
}

View File

@ -15,9 +15,16 @@
// You should have received a copy of the GNU General Public License
// along with Mocha.NET. If not, see <https://www.gnu.org/licenses/>.
using Mocha.Testing;
namespace Mocha.Core.Storage.Binary;
public class PersistenceTests : OmsTestsBase
public enum BinaryOmsStorageAttributeType : int
{
Text = 4,
Boolean = 5,
Numeric = 20,
Date = 21,
XML = 22,
Currency = 99,
File = 822,
RichText = 2737
}

View File

@ -0,0 +1,33 @@
// Copyright (C) 2025 Michael Becker <alcexhim@gmail.com>
//
// 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 <https://www.gnu.org/licenses/>.
namespace Mocha.Core.Storage.Binary;
public enum BinaryOmsStorageOpCode : byte
{
Invalid = 0x00,
CreateInstance = 0xC1,
DeleteInstance = 0xC0,
AssignAttribute = 0xA1,
ClearAttribute = 0xA0,
AssignRelationship = 0xB1,
RemoveRelationship = 0xB2,
ClearRelationship = 0xB0
}

View File

@ -1,138 +0,0 @@
// Copyright (C) 2025 Michael Becker <alcexhim@gmail.com>
//
// 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 <https://www.gnu.org/licenses/>.
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<InstanceHandle> 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)
{
}
}

View File

@ -0,0 +1,25 @@
// Copyright (C) 2025 Michael Becker <alcexhim@gmail.com>
//
// This file is part of mochalite-dotnet.
//
// mochalite-dotnet 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.
//
// mochalite-dotnet 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 mochalite-dotnet. If not, see <https://www.gnu.org/licenses/>.
namespace Mocha.Core.Storage;
public class CustomDefinition
{
public InstanceReference ClassInstance { get; set; } = null;
public Dictionary<string, InstanceReference> Attributes { get; } = new Dictionary<string, InstanceReference>();
public Dictionary<string, InstanceReference> Relationships { get; } = new Dictionary<string, InstanceReference>();
}

View File

@ -0,0 +1,342 @@
// Copyright (C) 2025 Michael Becker <alcexhim@gmail.com>
//
// This file is part of mochalite-dotnet.
//
// mochalite-dotnet 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.
//
// mochalite-dotnet 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 mochalite-dotnet. If not, see <https://www.gnu.org/licenses/>.
using System.Data;
using System.Globalization;
using System.IO.Pipes;
using System.Linq.Expressions;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json.Nodes;
using System.Threading.Tasks.Dataflow;
using Mocha.Core;
namespace Mocha.Core.Storage.Json;
public class JsonOmsStorage : OmsStorage
{
private Stream _Stream;
public Stream Stream { get { return _Stream; } }
public List<JsonObject> UnresolvedCustomDefinitions { get; } = new List<JsonObject>();
protected JsonObject? MakeInstanceObj(InstanceReference instance)
{
if (instance.InstanceKey == InstanceKey.Empty && instance.GlobalIdentifier == Guid.Empty)
return null;
JsonObject o = new JsonObject();
if (instance.InstanceKey != InstanceKey.Empty)
{
o.Add("instanceKey", instance.InstanceKey.ToString());
}
if (instance.GlobalIdentifier != Guid.Empty)
{
o.Add("globalIdentifier", instance.GlobalIdentifier.ToString());
}
return o;
}
public JsonOmsStorage(Stream stream, bool readOnly = false) : base(readOnly)
{
_Stream = stream;
_obj.Add("type", "journal");
_obj.Add("items", _ary);
}
public static JsonOmsStorage FromPath(string path)
{
string[] jsonfiles = System.IO.Directory.GetFiles(path, "*.json", SearchOption.AllDirectories);
StringBuilder content = new StringBuilder();
content.Append("{\"type\": \"journal\", \"items\": [");
for (int i = 0; i < jsonfiles.Length; i++)
{
content.Append(System.IO.File.ReadAllText(jsonfiles[i]));
if (i < jsonfiles.Length - 1)
{
content.Append(',');
}
}
content.Append("]}");
MemoryStream ms = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(content.ToString()));
return new JsonOmsStorage(ms, true);
}
private JsonArray _ary = new JsonArray();
private JsonObject _obj = new JsonObject();
protected override void FlushInternal()
{
Stream.Seek(0, SeekOrigin.Begin);
string text = _obj.ToJsonString();
byte[] data = System.Text.Encoding.UTF8.GetBytes(text);
Stream.Write(data, 0, data.Length);
}
protected override void ClearRelationshipInternal(InstanceReference sourceInstance, InstanceReference relationshipInstance, DateTime effectiveDate)
{
JsonObject o = new JsonObject();
o.Add("type", "clearRelationship");
o.Add("sourceInstance", MakeInstanceObj(sourceInstance));
o.Add("relationshipInstance", MakeInstanceObj(relationshipInstance));
o.Add("effectiveDate", effectiveDate.ToString("O"));
_ary.Add(o);
}
protected override void CreateInstanceInternal(InstanceReference instance, DateTime effectiveDate)
{
JsonObject o = new JsonObject();
o.Add("type", "createInstance");
o.Add("instanceKey", instance.InstanceKey.ToString());
o.Add("globalIdentifier", instance.GlobalIdentifier.ToString());
o.Add("effectiveDate", effectiveDate.ToString("O"));
_ary.Add(o);
}
protected override void WriteAttributeValueInternal(InstanceReference sourceInstance, InstanceReference attributeInstance, object value, DateTime effectiveDate)
{
JsonObject o = new JsonObject();
o.Add("type", "assignAttribute");
o.Add("sourceInstance", MakeInstanceObj(sourceInstance));
o.Add("attributeInstance", MakeInstanceObj(attributeInstance));
o.Add("value", JsonValue.Create(value));
o.Add("effectiveDate", effectiveDate.ToString("O"));
_ary.Add(o);
}
protected override void WriteRelationshipInternal(InstanceReference sourceInstance, InstanceReference relationshipInstance, IEnumerable<InstanceReference> targetInstances, DateTime effectiveDate, bool remove)
{
JsonObject o = new JsonObject();
o.Add("type", "assignRelationship");
o.Add("sourceInstance", MakeInstanceObj(sourceInstance));
o.Add("relationshipInstance", MakeInstanceObj(relationshipInstance));
JsonArray ary = new JsonArray();
foreach (InstanceReference inst in targetInstances)
{
ary.Add(MakeInstanceObj(inst));
}
o.Add("targetInstances", ary);
o.Add("effectiveDate", effectiveDate.ToString("O"));
_ary.Add(o);
}
protected override void LoadInternal(Oms oms)
{
StreamReader sr = new StreamReader(Stream);
string text = sr.ReadToEnd();
JsonObject? obj = (JsonObject?)JsonNode.Parse(text);
LoadObject(obj, oms);
}
private Dictionary<string, CustomDefinition> customDefinitions = new Dictionary<string, CustomDefinition>();
private void LoadObject(JsonObject? obj, Oms oms)
{
if (obj == null) return;
if (obj.ContainsKey("type"))
{
JsonNode? n = obj["type"];
if (n != null)
{
string tagName = n.GetValue<string>();
if (tagName.Equals("journal"))
{
JsonArray? ary = obj["items"] as JsonArray;
if (ary != null)
{
foreach (JsonNode? n2 in ary)
{
if (n2 is JsonObject o2)
{
LoadObject(o2, oms);
}
}
}
}
else if (tagName.Equals("createInstance"))
{
InstanceReference? ir = ParseInstanceReference(obj);
InstanceHandle ih = oms.UnsafeCreateInstance(ir);
}
else if (tagName.Equals("clearRelationship"))
{
oms.ClearRelationship(oms.GetInstance(ParseInstanceReference(obj["sourceInstance"] as JsonObject)), oms.GetInstance(ParseInstanceReference(obj["relationshipInstance"] as JsonObject)));
}
else if (tagName.Equals("assignRelationship"))
{
List<InstanceHandle> tgts = new List<InstanceHandle>();
foreach (JsonNode n1 in (obj["targetInstances"] as JsonArray))
{
tgts.Add(oms.GetInstance(ParseInstanceReference(n1 as JsonObject)));
}
oms.AssignRelationship(oms.GetInstance(ParseInstanceReference(obj["sourceInstance"] as JsonObject)), oms.GetInstance(ParseInstanceReference(obj["relationshipInstance"] as JsonObject)), tgts);
}
else if (tagName.Equals("assignAttribute"))
{
object? value = obj["value"].GetValue<string>();
DateTime effectiveDate = DateTime.Parse(obj["effectiveDate"].GetValue<string>());
oms.SetAttributeValue(oms.GetInstance(ParseInstanceReference(obj["sourceInstance"] as JsonObject)), oms.GetInstance(ParseInstanceReference(obj["attributeInstance"] as JsonObject)), value, effectiveDate);
}
else if (tagName.Equals("instance"))
{
CustomDefinition? cd = null;
if (obj.ContainsKey("instanceKey"))
{
InstanceKey inst = InstanceKey.Parse(obj["instanceKey"].GetValue<string>());
if (obj.ContainsKey("customTagName"))
{
cd = new CustomDefinition();
cd.ClassInstance = new InstanceReference(inst);
customDefinitions[obj["customTagName"].GetValue<string>()] = cd;
}
if (obj.ContainsKey("attributes"))
{
JsonArray ary = obj["attributes"].AsArray();
foreach (JsonObject? node in ary)
{
if (node == null) continue;
if (node.ContainsKey("instanceKey"))
{
if (node.ContainsKey("value"))
{
InstanceKey att = InstanceKey.Parse(node["instanceKey"].GetValue<string>());
if (node["value"].GetValueKind() == System.Text.Json.JsonValueKind.String)
{
oms.SetAttributeValue(oms.GetInstance(inst), oms.GetInstance(att), node["value"].GetValue<string>());
}
}
if (cd != null && node.ContainsKey("customTagName"))
{
cd.Attributes[node["customTagName"].GetValue<string>()] = new InstanceReference(InstanceKey.Parse(node["instanceKey"].GetValue<string>()));
}
}
}
}
if (obj.ContainsKey("relationships"))
{
JsonArray ary = obj["relationships"].AsArray();
foreach (JsonObject? node in ary)
{
if (node == null) continue;
if (node.ContainsKey("relationshipInstanceKey"))
{
InstanceKey rel = InstanceKey.Parse(node["relationshipInstanceKey"].GetValue<string>());
if (cd != null && node.ContainsKey("customTagName"))
{
cd.Attributes[node["customTagName"].GetValue<string>()] = new InstanceReference(InstanceKey.Parse(node["relationshipInstanceKey"].GetValue<string>()));
}
if (node.ContainsKey("targetInstances"))
{
JsonArray ary1 = node["targetInstances"].AsArray();
List<InstanceHandle> irs = new List<InstanceHandle>();
foreach (JsonObject? node1 in ary1)
{
InstanceReference ir = ParseInstanceReference(node1);
if (ir != null)
{
irs.Add(oms.GetInstance(ir));
}
}
oms.AssignRelationship(oms.GetInstance(inst), oms.GetInstance(rel), irs);
}
}
}
}
}
}
else
{
if (customDefinitions.ContainsKey(tagName))
{
CreateCustomDefinition(oms, obj, customDefinitions[tagName]);
}
else
{
UnresolvedCustomDefinitions.Add(obj);
}
}
}
}
}
protected override void ResolveDependenciesInternal(Oms oms)
{
base.ResolveDependenciesInternal(oms);
foreach (JsonObject obj in UnresolvedCustomDefinitions)
{
if (obj.ContainsKey("type"))
{
JsonNode? n = obj["type"];
if (n != null)
{
string tagName = n.GetValue<string>();
if (customDefinitions.ContainsKey(tagName))
{
CreateCustomDefinition(oms, obj, customDefinitions[tagName]);
}
}
}
}
}
private void CreateCustomDefinition(Oms oms, JsonObject obj, CustomDefinition cd)
{
InstanceHandle inst = oms.CreateInstanceOf(oms.GetInstance(cd.ClassInstance));
foreach (KeyValuePair<string, InstanceReference> kvp in cd.Attributes)
{
if (obj.ContainsKey(kvp.Key))
{
JsonNode n1 = obj[kvp.Key];
oms.SetAttributeValue(inst, oms.GetInstance(kvp.Value), n1.GetValue<string>());
}
}
}
private InstanceReference? ParseInstanceReference(JsonObject? obj)
{
if (obj == null)
return null;
InstanceKey instanceKey = InstanceKey.Empty;
Guid globalIdentifier = Guid.Empty;
if (obj.ContainsKey("instanceKey"))
{
instanceKey = InstanceKey.Parse(obj["instanceKey"].GetValue<string>());
}
if (obj.ContainsKey("globalIdentifier"))
{
globalIdentifier = Guid.Parse(obj["globalIdentifier"].GetValue<string>());
}
return new InstanceReference(instanceKey, globalIdentifier);
}
}

View File

@ -1,147 +0,0 @@
// Copyright (C) 2025 Michael Becker <alcexhim@gmail.com>
//
// 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 <https://www.gnu.org/licenses/>.
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<InstanceHandle> 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")
{
}
}
}
}
}
}

View File

@ -22,8 +22,8 @@ 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)
public StreamOmsStorage(string path, bool readOnly) : this(System.IO.File.Open(path, FileMode.Create, FileAccess.ReadWrite, FileShare.Read), readOnly) { }
public StreamOmsStorage(Stream stream, bool readOnly) : base(readOnly)
{
Stream = stream;
}

View File

@ -56,26 +56,6 @@ public abstract class OmsTestsBase
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}");
/// <summary>
/// Test Class.has Test Class 2
/// </summary>
/// <returns></returns>
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.";
[SetUp]
public void Setup()
{
ReloadOms();
// create the Test Class
InstanceHandle c_Class = Oms.GetInstance(KnownInstanceGuids.Classes.Class);
@ -101,6 +81,26 @@ public abstract class OmsTestsBase
Relationship? irTestRelationship = Oms.CreateRelationship(irTestClass, "has", irTestClass2, TEST_REL_GUID, true, "for", TEST_REL2_GUID);
Assert.That(irTestRelationship, Is.Not.Null);
}
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}");
/// <summary>
/// Test Class.has Test Class 2
/// </summary>
/// <returns></returns>
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.";
[SetUp]
public void Setup()
{
ReloadOms();
AfterSetup();
}

View File

@ -0,0 +1,58 @@
// Copyright (C) 2025 Michael Becker <alcexhim@gmail.com>
//
// 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 <https://www.gnu.org/licenses/>.
namespace Mocha.Core.Tests.Storage;
using Mocha.Core;
using Mocha.Core.Storage;
using Mocha.Core.Storage.Binary;
using Mocha.Testing;
public class BinaryStorageTests : OmsTestsBase
{
[Test]
public void BinaryOmsStorage_MemoryStream_Test()
{
System.IO.MemoryStream ms = new MemoryStream();
Oms.Storages.Add(new BinaryOmsStorage(ms));
string value = Oms.GetAttributeValue<string>(Oms.GetInstance(KnownInstanceGuids.Classes.Class), Oms.GetInstance(KnownAttributeGuids.Text.TargetURL));
Assert.That(value, Is.Null);
Oms.SetAttributeValue(Oms.GetInstance(KnownInstanceGuids.Classes.Class), Oms.GetInstance(KnownAttributeGuids.Text.TargetURL), "mahek_spamss");
value = Oms.GetAttributeValue<string>(Oms.GetInstance(KnownInstanceGuids.Classes.Class), Oms.GetInstance(KnownAttributeGuids.Text.TargetURL));
Assert.That(value, Is.EqualTo("mahek_spamss"));
Oms.Storages[0].Flush();
ms.Flush();
byte[] data = ms.ToArray();
ReloadOms();
value = Oms.GetAttributeValue<string>(Oms.GetInstance(KnownInstanceGuids.Classes.Class), Oms.GetInstance(KnownAttributeGuids.Text.TargetURL));
Assert.That(value, Is.Null);
ms = new MemoryStream(data);
Oms.Storages.Add(new BinaryOmsStorage(ms));
Oms.ReloadStorages();
value = Oms.GetAttributeValue<string>(Oms.GetInstance(KnownInstanceGuids.Classes.Class), Oms.GetInstance(KnownAttributeGuids.Text.TargetURL));
Assert.That(value, Is.EqualTo("mahek_spamss"));
}
}

View File

@ -16,6 +16,7 @@
// along with Mocha.NET. If not, see <https://www.gnu.org/licenses/>.
using Mocha.Core.Storage;
using Mocha.Core.Storage.Json;
using Mocha.Testing;
namespace Mocha.Core.Tests.Storage;
@ -66,5 +67,16 @@ public class JsonStorageTests : OmsTestsBase
// 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));
// and check that the attribute was set correctly
v = Oms.GetAttributeValue<string>(test, Oms.GetInstance(KnownAttributeGuids.Text.Name));
Assert.That(v, Is.EqualTo("PICADILLY"));
// and check relationships were assigned properly
IEnumerable<InstanceHandle> ihs = Oms.GetRelatedInstances(test, Oms.GetInstance(TEST_REL_GUID));
InstanceHandle ihC2 = Oms.GetInstance(TEST_CLASS2_GUID);
InstanceHandle ihI2 = Oms.GetInstancesOf(ihC2).First();
Assert.That(ihs.First(), Is.EqualTo(ihI2));
}
}