slight improvements to mocha ui

This commit is contained in:
Michael Becker 2024-03-22 15:51:09 -04:00
parent ca1269f84d
commit 2df465286a
29 changed files with 1305 additions and 6 deletions

View File

@ -9,16 +9,38 @@ from getopt import getopt
if __name__ == "__main__": if __name__ == "__main__":
port = 8081 port = 8081
libraries = [ ]
(opts, remaining) = getopt(sys.argv[1:], "p", [ "port=" ]) (opts, remaining) = getopt(sys.argv[1:], "p", [ "port=", "library=" ])
for opt in opts: for opt in opts:
if opt[0] == "--port": if opt[0] == "--port":
port = int(opt[1]) port = int(opt[1])
print("Mocha User Interface Service - running on port", port) print("Mocha User Interface Service v2.1")
oms = MemoryOms() from mocha.web.manager import ServerManager
svrmgr = ServerManager()
server = WebServer(("127.0.0.1", port), oms) for library in remaining:
server.serve_forever() svrmgr.add_server_config(library)
svrmgr.start()
# from mocha.lib.LibraryManager import MochaLibraryManager
# manager = MochaLibraryManager()
# from mocha.lib.parser.YAMLLibraryParser import YAMLLibraryParser
# yamlmgr = YAMLLibraryParser(manager)
# print("loading entity definitions...")
# for library in remaining:
# print("\t" + library)
# yamlmgr.load_entity_definitions_from_file(library)
# print("loading instances...")
# for library in remaining:
# print("\t" + library)
# yamlmgr.load_instances_from_file(library)
# oms = MemoryOms()

View File

@ -0,0 +1,57 @@
from .Normalization import Normalization
from framework import Guid
class InstanceCache:
def __init__(self):
self.next_inst_id = dict()
self.next_inst_id[1] = 1
self.inst_indices = dict()
self.inst_guids = dict()
self.dbids_gid = dict()
def add_instance(self, inst_key, inst_gid):
self.inst_guids[inst_key] = inst_gid
self.inst_indices[inst_gid] = inst_key
if not inst_key[0] in self.next_inst_id:
self.next_inst_id[inst_key[0]] = 1
if inst_key[1] >= self.next_inst_id[inst_key[0]]:
self.next_inst_id[inst_key[0]] = inst_key[1] + 1
def get_global_identifier(self, inst_key):
return self.inst_guids[inst_key]
def get_instance_key(self, inst_gid):
return self.inst_indices[inst_gid]
def get_next_instance_id(self, class_index):
if not class_index in self.next_inst_id:
# this is the first instance of this class
self.next_inst_id[class_index] = 1
val = self.next_inst_id[class_index]
self.next_inst_id[class_index] += 1
return val
def has_instance_by_global_identifier(self, gid):
return gid in self.inst_indices
def count(self) -> int:
"""
Returns the number of instances stored in this OMS.
"""
return len(self.inst_indices)
def get_instance_keys(self):
return self.inst_indices
def set_database_id_by_global_identifier(self, gid : Guid, dbid):
self.dbids_gid[Normalization.normalize_uuid(gid.get_value())] = dbid
# self.dbids_idx[self.inst_indices[gid]] = dbid
def get_database_id_by_global_identifier(self, gid : Guid):
return self.dbids_gid[Normalization.normalize_uuid(gid.get_value())]

View File

@ -0,0 +1,442 @@
import MySQLdb
from .parser import XMLLibraryParser, JSONLibraryParser, YAMLLibraryParser
from .LibraryOperation import LibraryOperation
from .operations.AssignAttributeOperation import AssignAttributeOperation
from .operations.AssignRelationshipOperation import AssignRelationshipOperation
from .operations.PrepareInstanceOperation import PrepareInstanceOperation
from .operations.StoredProcedureOperation import StoredProcedureOperation
from .Normalization import Normalization
from framework import Guid
from mocha.definitions import KnownClassGuids, KnownAttributeGuids, KnownInstanceGuids, KnownRelationshipGuids
from .InstanceCache import InstanceCache
from .SQLExpression import SQLExpression
# from redis import Redis
class MochaLibraryManager:
def __init__(self):
self.entityReferences = dict()
self.entityReferenceFileNames = dict()
self.db = None
self._instances = []
self._attOps = []
self._relOps = []
self.redis = None
def connect(self, hostname, database, username, password):
self.db = MySQLdb.connect(host=hostname, user=username, passwd=password, db=database)
# self.redis = Redis(host=hostname, port=6379, decode_responses=True)
def process_instance_ops(self):
# this version is much faster as it prepares everything in advanced on the CPU before
# pushing it out to the database
instances = InstanceCache()
for key in self._instances:
(class_gid, inst_gid, class_index, inst_index) = key
# first import all the known classes
if class_gid is not None and class_index is not None:
# print ("known class import")
# print ("class_gid = " + class_gid)
# print ("inst_gid = " + inst_gid + " (" + str(class_index) + "$" + str(inst_index) + ")")
if not instances.has_instance_by_global_identifier(class_gid):
instances.add_instance((1, class_index), class_gid)
for key in self._instances:
(class_gid, inst_gid, class_index, inst_index) = key
# now get all the classes without specified IIDs
if class_gid is not None and class_index is None:
# print ("unknown class import")
if not instances.has_instance_by_global_identifier(class_gid):
class_index = instances.get_next_instance_id(class_index)
else:
class_index = instances.get_instance_key(class_gid)[1]
if inst_index is None:
inst_index = instances.get_next_instance_id(class_index)
instances.add_instance((1, inst_index), inst_gid)
# print ("class_gid = " + class_gid)
# print ("inst_gid = " + inst_gid + " (" + str(1) + "$" + str(inst_index) + ")")
for key in self._instances:
(class_gid, inst_gid, class_index, inst_index) = key
if class_gid is not None and class_index is not None and inst_gid is not None and inst_index is not None:
# we know all there is to know about this one
pass
elif class_gid is not None and inst_gid is not None:
if class_index is None:
# we need to know the class index
if instances.has_instance_by_global_identifier(class_gid):
class_index = instances.get_instance_key(class_gid)[1]
else:
class_index = instances.get_next_instance_id(1)
if inst_index is None:
# we need to know the instance index as well
if instances.has_instance_by_global_identifier(inst_gid):
inst_index = instances.get_instance_key(inst_gid)[1]
else:
inst_index = instances.get_next_instance_id(class_index)
else:
print("error: not implemened class_gid is None or inst_gid is None")
instances.add_instance((class_index, inst_index), inst_gid)
print (str(instances.count()) + " instances preloaded")
c = 0
query = "INSERT INTO mocha_instances (tenant_id, class_id, inst_id, global_identifier) VALUES "
for inst_gid in instances.get_instance_keys():
(class_index, inst_index) = instances.get_instance_key(inst_gid)
print("preparing inst " + str(class_index) + "$" + str(inst_index) + " with guid " + inst_gid)
inst_key_str = str(class_index) + "$" + str(inst_index)
if self.redis is not None:
self.redis.set(inst_key_str + ".gid", Normalization.normalize_uuid(inst_gid))
self.redis.set(inst_key_str + ".attributes.count", "0")
self.redis.set(inst_key_str + ".relationships.count", "0")
self.redis.set(Normalization.normalize_uuid(inst_gid) + ".iid", inst_key_str)
self.redis.set(Normalization.normalize_uuid(inst_gid) + ".attributes.count", "0")
self.redis.set(Normalization.normalize_uuid(inst_gid) + ".relationships.count", "0")
query += "(1, " + str(class_index) + ", " + str(inst_index) + ", '" + Normalization.normalize_uuid(inst_gid) + "')"
if c < instances.count() - 1:
query += ", "
c += 1
print ("process_instance_ops: query was: ")
print (query)
self.db.query(query)
self.instances = instances
return True
def process_attribute_ops(self, ops):
query = "INSERT INTO mocha_attributes (tenant_id, src_inst_id, att_inst_id, usr_inst_id, att_effective_date, att_value) VALUES "
for op in ops:
instanceId = op.instanceId
attributeInstanceId = op.attributeInstanceId
value = op.value
if attributeInstanceId.get_value() == "":
print("skipping invalid blank attribute")
continue
print (instanceId.get_value() + " : " + attributeInstanceId.get_value() + " = '" + str(value) + "'")
# query += "(1, " \
# + "mocha_get_instance_by_global_identifier('" + Normalization.normalize_uuid(instanceId.get_value()) + "'), " \
# + "mocha_get_instance_by_global_identifier('" + Normalization.normalize_uuid(attributeInstanceId.get_value()) + "'), " \
# + "NULL, NOW(), " + SQLExpression.normalize_parm(value) + ")"
query += "(1, " \
+ str(self.instances.get_database_id_by_global_identifier(instanceId)) + ", " \
+ str(self.instances.get_database_id_by_global_identifier(attributeInstanceId)) + ", " \
+ "NULL, NOW(), " + SQLExpression.normalize_parm(value) + ")"
if ops.index(op) < len(ops) - 1:
query += ", "
# op.execute(self.db)
print ("process_attribute_ops: query was: ")
print (query)
self.db.query(query)
def process_relationship_ops(self, ops):
query = "INSERT INTO mocha_relationships (tenant_id, source_inst_id, relationship_inst_id, destination_inst_id, user_inst_id, effective_date) VALUES "
for op in ops:
instanceId = op.instanceId
relationshipInstanceId = op.relationshipInstanceId
targetInstanceId = op.targetInstanceId
print (instanceId.get_value() + " : " + relationshipInstanceId.get_value() + " = " + targetInstanceId.get_value())
query += "(1, " \
+ str(self.instances.get_database_id_by_global_identifier(instanceId)) + ", " \
+ str(self.instances.get_database_id_by_global_identifier(relationshipInstanceId)) + ", " \
+ str(self.instances.get_database_id_by_global_identifier(targetInstanceId)) + ", " \
+ "NULL, NOW() )"
if ops.index(op) < len(ops) - 1:
query += ", "
# op.execute(self.db)
print ("process_relationship_ops: query was: ")
print (query)
self.db.query(query)
def before_commit(self):
"""
Called before the unstructured information is sorted and written to the database.
---
This is your last chance to add instances, set attributes, and assign
relationships before the compilation is finalized. After this stage, calls made
to modify the compilation are undefined and will almost certainly not do what you
want.
"""
self.write_debug_information()
def write_debug_information(self):
"""
Writes debugging information, such as entity definitions ('&IDC_...;') and source
code definitions (filename, line number, column number, etc.)
"""
print ("preparing debug information (use --no-debug or undefine DEBUG or etc. [NOT IMPLEMENTED] to turn off)")
for key in self.entityReferences:
val = self.entityReferences[key]
gidEntityDefinition = Guid.create()
self.add_instance(IDC_EntityDefinition, gidEntityDefinition, None, None)
self.set_attribute_value(gidEntityDefinition, IDA_Name, key)
self.set_attribute_value(gidEntityDefinition, IDA_Value, val)
gidSourceDefinition = Guid.create()
self.add_instance(IDC_SourceDefinition, gidSourceDefinition, None, None)
self.set_attribute_value(gidSourceDefinition, IDA_DebugDefinitionFileName, self.entityReferenceFileNames[key])
self.assign_relationship(gidEntityDefinition, IDR_Instance__has__Source_Definition, gidSourceDefinition)
# Source Definition.has X
# self.add_instance(KnownClassGuids.CLASS, Guid.parse("{dc0a5dd2-22e0-471f-87cf-a5ef1b764efa}"), 1, index)
# self.assign_relationship(instGid, Guid.parse("{dc0a5dd2-22e0-471f-87cf-a5ef1b764efa}"), targetInstanceId)
def commit(self):
self.before_commit()
# first record all the instances
if not self.process_instance_ops():
return False
print("reading database ids...")
query = "SELECT id, global_identifier FROM mocha_instances"
cur = self.db.cursor()
cur.execute(query)
rows = cur.fetchall()
for (dbid, gid) in rows:
self.instances.set_database_id_by_global_identifier(Guid(gid), dbid)
print("assigning attributes...")
self.process_attribute_ops(self._attOps)
print("assigning relationships...")
self.process_relationship_ops(self._relOps)
hasSiblingRelationships = dict()
for op in self._relOps:
relationshipKey = self.instances
if op.relationshipInstanceId.get_value() == "{656110FF-4502-48B8-A7F3-D07F017AEA3F}":
# Relationship.has sibling Relationship
hasSiblingRelationships[op.instanceId.get_value()] = op.targetInstanceId
siblingOps = []
for op in self._relOps:
if op.relationshipInstanceId.get_value() in hasSiblingRelationships:
siblingOp = AssignRelationshipOperation(op.targetInstanceId, hasSiblingRelationships[op.relationshipInstanceId.get_value()], op.instanceId)
siblingOps.append(siblingOp)
# print ("assigning sibling relationship " + siblingOp.instanceId.get_value() + " . " + siblingOp.relationshipInstanceId.get_value() + " = " + siblingOp.instanceId.get_value())
# siblingOp.execute(self.db)
print("assigning sibling relationships...")
self.process_relationship_ops(siblingOps)
print("setting creation user...")
cur = self.db.cursor()
# cur.execute("UPDATE mocha_instances SET user_inst_id = mocha_get_instance_by_global_identifier(mocha_normalize_uuid('{B066A54B-B160-4510-A805-436D3F90C2E6}'))")
cur.execute("UPDATE mocha_attributes SET usr_inst_id = " + str(self.instances.get_database_id_by_global_identifier(Guid.parse("{B066A54B-B160-4510-A805-436D3F90C2E6}"))))
cur.execute("UPDATE mocha_relationships SET user_inst_id = " + str(self.instances.get_database_id_by_global_identifier(Guid.parse("{B066A54B-B160-4510-A805-436D3F90C2E6}"))))
# rows = cur.fetchall()
# print(rows)
# print ("ok , applying `Class.has Instance`...")
# for row in rows:
# print(row)
# id = row[0]
# tenant_id = row[1]
# class_id = row[2]
# inst_id = row[3]
# global_identifier = row[4]
# if class_id is not None and id is not None:
# # Class.has Instance
# query2 = ("CALL mocha_assign_relationship (" +
# "mocha_get_instance_by_key(1, " + str(class_id) + "), " +
# "mocha_get_instance_by_global_identifier(mocha_normalize_uuid('7EB41D3C-2AE9-4884-83A4-E59441BCAEFB'))" + ", " +
# str(id) + ", NULL, NULL)")
# cur.execute(query2)
# # Instance.for Class
# query3 = ("CALL mocha_assign_relationship (" +
# str(id) + ", " +
# "mocha_get_instance_by_global_identifier(mocha_normalize_uuid('494D5A6D-04BE-477B-8763-E3F57D0DD8C8'))" + ", " +
# "mocha_get_instance_by_key(1, " + str(class_id) + "), NULL, NULL)")
# cur.execute(query3)
self.db.commit()
def close(self):
self.db.close()
def print_error(self, cur):
rows = cur.fetchall()
for row in rows:
if 'error_description' in row:
print (row['error_description'])
def select_tenant(self, tenantName):
cur = self.db.cursor()
cur.execute("CALL mocha_select_tenant(mocha_get_tenant_by_name('" + tenantName + "'))")
self.print_error(cur)
def release_tenant(self):
cur = self.db.cursor()
cur.execute("CALL mocha_release_tenant()")
self.print_error(cur)
def assign_relationship(self, instanceId : Guid, relationshipInstanceId : Guid, targetInstanceId : Guid):
print("-- assigning relationship " + relationshipInstanceId.get_value() + " = '" + targetInstanceId.get_value() + "'")
self._relOps.append(AssignRelationshipOperation(instanceId, relationshipInstanceId, targetInstanceId))
# self._relOps.append(AssignRelationshipOperation(instanceId, relationshipInstanceId, targetInstanceId))
# query = "CALL mocha_assign_relationship(mocha_get_instance_by_global_identifier(mocha_normalize_uuid('" + instanceId.get_value() + "')), mocha_get_instance_by_global_identifier(mocha_normalize_uuid('" + relationshipInstanceId.get_value() + "')), mocha_get_instance_by_global_identifier(mocha_normalize_uuid('" + targetInstanceId.get_value() + "')), NULL, NULL);"
# print(query)
# cur.execute(query)
# self.print_error(cur)
def set_attribute_value(self, instanceId : Guid, attributeInstanceId : Guid, value):
print("-- assigning attribute " + attributeInstanceId.get_value() + " = '" + str(value) + "'")
self._attOps.append(AssignAttributeOperation(instanceId, attributeInstanceId, value))
def install_from_path(self, path):
from glob import glob
xl = XMLLibraryParser(self)
jl = JSONLibraryParser(self)
yl = YAMLLibraryParser(self)
#xml_files = glob(path + "/**/*.xml", recursive=True)
#for xml_file in xml_files:
#s xl.load_file(xml_file)
#json_files = glob(path + "/**/*.json", recursive=True)
#for json_file in json_files:
# jl.load_file(json_file)
yaml_files = sorted(glob(path + "/**/*.yaml", recursive=True))
if len(yaml_files) == 0:
print ("no files found ; does the path exist?")
return 3
# first, load the entity defs
for yaml_file in yaml_files:
yl.load_entity_definitions_from_file(yaml_file)
try:
# then, load instance definitions (also loads sugar elements into memory for later use)
for yaml_file in yaml_files:
yl.load_instances_from_file(yaml_file)
# finally, apply syntactic sugar
yl.apply_sugar()
return True
except NameError as ex:
print (ex)
rgx = "&(.*);"
# go through and get all entity references across all files
import re
import fileinput
stuff = []
for yaml_file in yaml_files:
f = open(yaml_file, "r")
text = f.read()
matches = re.findall(rgx, text)
for match in matches:
stuff.append(match)
f.close()
missingEntities = []
for stuf in stuff:
if not stuf in self.entityReferences:
if not stuf in missingEntities:
missingEntities.append(stuf)
if len(missingEntities) > 0:
print("\nNOTE: there were undefined referenced entities:\n")
for missingEntity in missingEntities:
print("\t" + missingEntity)
print("\n")
return False
def exists_entity_reference(self, name):
return name in self.entityReferences
def register_entity_reference(self, name, value, filename = None):
self.entityReferences[name] = value
self.entityReferenceFileNames[name] = filename
def define_entity_reference(self, name):
return self.entityReferences[name]
def expand_entity_references(self, value):
insideName = False
name = ""
retval = ""
for i in range(0, len(value)):
if value[i] == "&":
insideName = True
elif value[i] == ';':
insideName = False
if name in self.entityReferences:
retval += self.define_entity_reference(name)
else:
raise NameError("unknown entity ref '" + name + "'")
name = ""
elif insideName:
name += value[i]
else:
retval += value[i]
return retval
def add_instance(self, classGid : Guid, instGid : Guid, classIndex : int, index : int):
print("adding instance for class '" + classGid.get_value() + "' with globalid '" + instGid.get_value() + "' [" + str(index) + "]")
self._instances.append((classGid.get_value(), instGid.get_value(), classIndex, index))
# assign relationship `Instance.for Class`
self.assign_relationship(instGid, Guid.parse("{494D5A6D-04BE-477B-8763-E3F57D0DD8C8}"), classGid)
# assign relationship `Class.has Instance`
self.assign_relationship(classGid, Guid.parse("{7EB41D3C-2AE9-4884-83A4-E59441BCAEFB}"), instGid)

View File

@ -0,0 +1,21 @@
import MySQLdb
from MySQLdb.connections import Connection
class LibraryOperation:
def build_query(self):
return ''
def print_error(self, cur):
rows = cur.fetchall()
for row in rows:
if 'error_description' in row:
print (row['error_description'])
def execute(self, db : Connection):
cur = db.cursor()
query = self.build_query()
print(query)
cur.execute(query)
self.print_error(cur)

View File

@ -0,0 +1,5 @@
class Normalization:
@staticmethod
def normalize_uuid(val : str):
val = val.replace("{", "").replace("}", "").replace("-", "").strip().lower()
return val

View File

@ -0,0 +1,47 @@
from framework import Guid
from .Normalization import Normalization
class SQLExpression:
@staticmethod
def sqlescape(parm : str):
return parm.replace("'", "\\'")
@staticmethod
def normalize_parm(parm : str):
query = ""
if parm == None:
query += "NULL"
elif isinstance(parm, Guid):
gid = None
if parm is not None:
gid = "'" + Normalization.normalize_uuid(parm.get_value()) + "'"
else:
gid = "NULL"
query += gid
elif isinstance(parm, str):
query += "'" + SQLExpression.sqlescape(str(parm)) + "'"
else:
query += str(parm)
return query
@staticmethod
def to_string(parm):
return SQLExpression.normalize_parm(parm)
@staticmethod
def array_to_string(parms):
i = 0
query = ''
for parm in parms:
query += SQLExpression.to_string(parm)
if i < len(parms) - 1:
query += ", "
i += 1
return query

View File

@ -0,0 +1,20 @@
from .SQLExpression import SQLExpression
class SQLFunctionCall (SQLExpression):
def __init__(self, funcname, parms):
self.name = funcname
self.parms = parms
def get_name(self):
return self.name
def get_parameters(self):
return self.parms
def get_parameter_list(self):
return SQLExpression.array_to_string(self.parms)
def __str__(self):
return self.get_name() + "(" + self.get_parameter_list() + ")"

View File

@ -0,0 +1,12 @@
from .SQLExpression import SQLExpression
class SQLParameter (SQLExpression):
def __init__(self, name):
self.name = name
def get_name(self):
return self.name
def __str__(self):
return "@" + self.get_name()

View File

@ -0,0 +1,21 @@
from .StoredProcedureOperation import StoredProcedureOperation
from framework import Guid
from ..SQLParameter import SQLParameter
from ..SQLFunctionCall import SQLFunctionCall
class AssignAttributeOperation(StoredProcedureOperation):
def __init__(self, instanceId : Guid, attributeInstanceId : Guid, value):
self.instanceId = instanceId
self.attributeInstanceId = attributeInstanceId
self.value = value
def get_sp_name(self):
return "mocha_set_attribute_value"
def get_sp_parameters(self):
return [ SQLFunctionCall('mocha_get_instance_by_global_identifier', [ self.instanceId ]), SQLFunctionCall('mocha_get_instance_by_global_identifier', [ self.attributeInstanceId ]), str(self.value), None, None ]
#query = "CALL mocha_set_attribute_value(mocha_get_instance_by_global_identifier(mocha_normalize_uuid('" + instanceId.get_value() + "')), mocha_get_instance_by_global_identifier(mocha_normalize_uuid('" + attributeInstanceId.get_value() + "')), '" + str(value) + "', NULL, NULL);"
#print(query)
#cur.execute(query)
#self.print_error(cur)

View File

@ -0,0 +1,21 @@
from .StoredProcedureOperation import StoredProcedureOperation
from framework import Guid
from ..SQLParameter import SQLParameter
from ..SQLFunctionCall import SQLFunctionCall
class AssignRelationshipOperation(StoredProcedureOperation):
def __init__(self, instanceId : Guid, relationshipInstanceId : Guid, targetInstanceId : Guid):
self.instanceId = instanceId
self.relationshipInstanceId = relationshipInstanceId
self.targetInstanceId = targetInstanceId
def get_sp_name(self):
return "mocha_assign_relationship"
def get_sp_parameters(self):
return [ SQLFunctionCall('mocha_get_instance_by_global_identifier', [ self.instanceId ]), SQLFunctionCall('mocha_get_instance_by_global_identifier', [ self.relationshipInstanceId ]), SQLFunctionCall('mocha_get_instance_by_global_identifier', [ self.targetInstanceId ]), None, None ]
#query = "CALL mocha_set_attribute_value(mocha_get_instance_by_global_identifier(mocha_normalize_uuid('" + instanceId.get_value() + "')), mocha_get_instance_by_global_identifier(mocha_normalize_uuid('" + attributeInstanceId.get_value() + "')), '" + str(value) + "', NULL, NULL);"
#print(query)
#cur.execute(query)
#self.print_error(cur)

View File

@ -0,0 +1,17 @@
from .StoredProcedureOperation import StoredProcedureOperation
from ..SQLParameter import SQLParameter
from ..SQLFunctionCall import SQLFunctionCall
from ..Guid import Guid
class CreateClassOperation (StoredProcedureOperation):
def __init__(self, globalIdentifier : Guid, classIndex : int):
self.globalIdentifier = globalIdentifier
self.classIndex = classIndex
def get_sp_name(self):
return 'mocha_create_class'
def get_sp_parameters(self):
return [ self.classIndex, self.globalIdentifier, None, None, SQLParameter("dummy") ]

View File

@ -0,0 +1,17 @@
from .StoredProcedureOperation import StoredProcedureOperation
from ..Guid import Guid
from ..SQLParameter import SQLParameter
from ..SQLFunctionCall import SQLFunctionCall
class CreateInstanceOperation(StoredProcedureOperation):
def __init__(self, globalIdentifier : Guid, classIndex : int, instanceIndex : int):
self.globalIdentifier = globalIdentifier
self.classIndex = classIndex
self.instanceIndex = instanceIndex
def get_sp_name(self):
return 'mocha_create_instance'
def get_sp_parameters(self):
return [ self.globalIdentifier, self.classIndex, self.instanceIndex, None, None, SQLParameter('assigned_inst_id') ]

View File

@ -0,0 +1,36 @@
from .StoredProcedureOperation import StoredProcedureOperation
from framework import Guid
from ..SQLParameter import SQLParameter
from ..SQLFunctionCall import SQLFunctionCall
class PrepareInstanceOperation(StoredProcedureOperation):
def __init__(self, classGlobalIdentifier : Guid, globalIdentifier : Guid, classIndex : int, instanceIndex : int):
self.classGlobalIdentifier = classGlobalIdentifier
self.globalIdentifier = globalIdentifier
self.classIndex = classIndex
self.instanceIndex = instanceIndex
def get_sp_name(self):
return "mocha_prepare_instance"
def get_sp_parameters(self):
parms = []
globalId = None
if self.globalIdentifier is not None:
globalId = self.globalIdentifier
classGlobalId = None
if self.classGlobalIdentifier is not None:
classGlobalId = self.classGlobalIdentifier
strCid = None
if self.classIndex is not None:
strCid = self.classIndex
strIid = None
if self.instanceIndex is not None:
strIid = self.instanceIndex
return [strCid, strIid, classGlobalId, globalId, None, None, SQLParameter("p_assigned_inst_id")]

View File

@ -0,0 +1,23 @@
import MySQLdb
from ..LibraryOperation import LibraryOperation
from MySQLdb.connections import Connection
from ..SQLExpression import SQLExpression
from ..SQLParameter import SQLParameter
from ..SQLFunctionCall import SQLFunctionCall
class StoredProcedureOperation (LibraryOperation):
def get_sp_name(self):
return ''
def get_sp_parameters(self):
return []
def build_query(self):
query = "CALL " + self.get_sp_name() + "("
parms = self.get_sp_parameters()
query += SQLExpression.array_to_string(parms)
query += ")"
return query

View File

@ -0,0 +1,30 @@
from .LibraryParser import LibraryParser
class JSONLibraryParser (LibraryParser):
def cstyle_strip_comments(self, val):
val2 = ""
# possible values for comment: 0 = none, 1 = multiline, 2 = singleline
comment = 0
xr = iter(range(0, len(val) - 1))
for i in xr:
if val[i] == '\n' and comment == 2:
comment = 0
check = val[i:i+2]
if check == "/*" and comment == 0:
comment = 1
next(xr)
elif check == "*/" and comment == 1:
comment = 0
next(xr)
elif check == "//":
comment = 2
next(xr)
elif comment == 0:
val2 += val[i]
val2 += val[-1:]
return val2

View File

@ -0,0 +1,4 @@
class LibraryParser():
def __init__(self, manager):
self.__manager = manager

View File

@ -0,0 +1,5 @@
class MochaSyntaxError(RuntimeError):
def __init__(self, *args):
RuntimeError.__init__(self, args)
pass

View File

@ -0,0 +1,46 @@
from .LibraryParser import LibraryParser
from xml.dom import minidom, Node
from xml.dom.minidom import Entity
class XMLLibraryParser(LibraryParser):
def load_instance(self, xmlnode):
print("instance : [" + xmlnode.getAttribute("id") + "]")
def load_library(self, xmlnode):
"""
Loads a library from the specified XML node.
"""
raise NotImplementedError()
if xmlnode.tagName != "library":
return
id = xmlnode.getAttribute("id")
for child in xmlnode.childNodes:
if child.tagName == "instances":
for child2 in child.childNodes:
self.load_library_instance_from_xml(child)
def load_file(self, filename):
print("loading xml from " + filename + "... ")
dom = minidom.getDOMImplementation()
dt = dom.createDocumentType("mocha", "-//MBS//DTD Mocha 1.0 Dev//EN", "https://doctype.schemas.alcetech.net/Mocha/1.0/mocha-1.0.dtd")
dt.entities.setNamedItem(Entity("IDC_ReturnInstanceSetMethodBinding", "{AADC20F9-7559-429B-AEF0-97E059295C76}", None, None))
dom = minidom.parse(filename)
if (dom.documentElement.tagName != "mocha"):
print(filename + ": top-level tag is not 'mocha'")
return
for child in dom.documentElement.childNodes:
if child.nodeType == Node.ELEMENT_NODE:
if child.tagName == "libraries":
for child2 in child.childNodes:
self.load_library(child)
print ("\n")

View File

@ -0,0 +1,345 @@
from .LibraryParser import LibraryParser
from framework import Guid
from .MochaSyntaxError import MochaSyntaxError
class YAMLLibraryParser (LibraryParser):
def __init__(self, manager):
LibraryParser.__init__(self, manager)
self.yamlIds = dict()
self.templates = dict()
# these two are special reserved tagNames
# self.yamlIds["entityDefinitions"] = None
# self.yamlIds["instance"] = { }
self.manager = manager
self._instances_awaiting_sugar = []
def apply_sugar(self):
print("applying syntactic sugar to remaining instances...")
# _instances_also_awaiting_sugar = []
for inst in self._instances_awaiting_sugar:
print("applying sugar from '" + inst["from_file_name"] + "'")
self.load_instance(inst)
print("\n")
# do recursively until we're all out of instances awaiting sugar
# if len(self._instances_awaiting_sugar) > 0:
# self.apply_sugar()
def load_library(self, elem):
print("loading library from elem")
def load_tenant(self, elem):
print("loading tenant from elem")
def find_custom_tag_name(self, elem) -> (str, Guid):
# return tuple (tagname, value)
if "instance" in elem:
return ("instance", Guid.parse(self.manager.expand_entity_references(elem["instance"])))
for key in self.yamlIds:
if key in elem:
k = str(key)
v = Guid.parse(self.manager.expand_entity_references(elem[key]))
return (k, v)
return (None, None)
def load_entity_definitions_from_file(self, filename):
print("loading entity defs from " + filename)
import yaml
with open(filename, 'r') as file:
content = yaml.safe_load_all(file)
for doc in content:
for elem in doc:
if "entityDefinitions" in elem:
entities = elem["entityDefinitions"]
if not entities is None:
for entity in entities:
for key in entity:
if self.manager.exists_entity_reference(key):
print("duplicate entity definition: '" + key + "'")
exit(1)
self.manager.register_entity_reference(key, entity[key], filename)
def load_instance_with_template(self, elem, template):
(parentCreatorKey, parentInstanceId) = self.find_custom_tag_name(template)
(creatorKey, instanceId) = self.find_custom_tag_name(elem)
self.apply_template(elem, template, instanceId)
def assign_relationship_value(self, instanceId, rel, rel_iid, relationshipValue):
if (isinstance(relationshipValue, str)):
# single instance, this should be a GUID or entityref
# we should only need to refer to existing instance in this case
relationshipValueInst = Guid.parse(self.manager.expand_entity_references(relationshipValue))
if "instance" in rel:
self.manager.assign_relationship(instanceId, Guid.parse(self.manager.expand_entity_references(rel["instance"])), relationshipValueInst)
else:
print("no relationship instance specified for relationship sugar")
else:
# dynamically created instance
#FIXME: this 'instance' isn't always the tag name, this can be subject to customTagName as well!and should be processed accordingly
(relinst_key, relinst_iid) = self.find_custom_tag_name(relationshipValue)
if relinst_key is not None:
# for example, 'relinst_key' is 'elementContent' and 'relinst_iid' is '{8ECA14A4...}'
if rel_iid is not None:
print("found customTagName '" + str(relinst_key) + "' with value '" + str(relinst_iid.get_value()) + "'")
# relval = Guid.parse(self.manager.expand_entity_references(elem[customTagName]))
# we need to create the new instance and assign relationship
self.manager.assign_relationship(instanceId, rel_iid, relinst_iid)
else:
if "globalIdentifier" in relationshipValue:
globalIdentifier = Guid.parse(relationshipValue["globalIdentifier"])
else:
globalIdentifier = Guid.create()
print("creating new instance for relationship '%s' with globalid '%s'" % (rel_iid, globalIdentifier.get_value()))
createsInstanceOf = None
if "type" in relationshipValue:
print("found relationship override type '%s'" % (relationshipValue['type']))
createsInstanceOf = Guid.parse(self.manager.expand_entity_references(relationshipValue["type"]))
elif "customTagNameCreatesInstanceOf" in rel:
createsInstanceOf = Guid.parse(self.manager.expand_entity_references(rel["customTagNameCreatesInstanceOf"]))
if not createsInstanceOf is None:
# create the new instance
self.manager.add_instance(createsInstanceOf, globalIdentifier, None, None)
# assign relationships
self.manager.assign_relationship(instanceId, rel_iid, globalIdentifier)
# self.manager.assign_relationship(globalIdentifier, relationshipInstanceId, instanceId)
# FIXME: apply relationships from the parent class template
#? NOTE: You MUST specify 'registerForTemplate: yes' on the template definition for
#? sibling relationships to be properly applied!
if createsInstanceOf.get_value() in self.templates:
print("applying template for createsInstanceOf '%s'" % (createsInstanceOf.get_value()))
createsInstanceOfTemplate = self.templates[createsInstanceOf.get_value()]
self.apply_template(relationshipValue, createsInstanceOfTemplate, globalIdentifier)
else:
print("no template registered for createsInstanceOf '%s'" % (createsInstanceOf.get_value()))
else:
print ("neither createsInstanceOf nor override 'type' present for relationship value")
def apply_template(self, elem, template, instanceId):
if "value" in elem:
print("VALUE found in ELEM:")
print(elem["value"])
if "attributes" in template:
attrs = template["attributes"]
if isinstance(attrs, list):
for attr in attrs:
if "customTagName" in attr:
customTagName = attr["customTagName"]
if customTagName in elem:
attrinst = Guid.parse(self.manager.expand_entity_references(attr["instance"]))
attrval = elem[customTagName]
self.manager.set_attribute_value(instanceId, attrinst, attrval)
if "relationships" in template:
rels = template["relationships"]
for rel in rels:
if "instance" in rel:
rel_iid = rel["instance"] # the globalId of the relationship instance
else:
print("no relationship instance specified for relationship sugar")
continue
if "customTagName" in rel:
customTagName = rel["customTagName"]
if customTagName in elem:
relationshipValue = elem[customTagName]
if relationshipValue is None:
continue
if isinstance(relationshipValue, list):
# multiple instances
for v in relationshipValue:
self.assign_relationship_value(instanceId, rel, Guid.parse(self.manager.expand_entity_references(rel_iid)), v)
else:
self.assign_relationship_value(instanceId, rel, Guid.parse(self.manager.expand_entity_references(rel_iid)), relationshipValue)
def get_instance_sugar_for_elem(self, elem):
if "instance" in elem:
return (elem["instance"], "instance", None)
else:
for key in self.yamlIds:
# no 'instance', see if we
if key in elem:
template = self.yamlIds[key]
templateKey = key
return (elem[templateKey], templateKey, template)
return (None, None, None) # should never get here
def load_instance(self, elem, elemParent = None):
# 'class' and 'relationship' are both at the 'same level'
# so we can't define 'relationship' without first defining 'class'
# we could try to keep looping as long as we're still getting "unknown tag name"
# (instanceID is None), but we could get stuck that way...
# for now, let's just forward define important classes (e.g. IDC_Relationship)
# all of these problems could be avoided by simply
# LOADING EVERYTHING INTO MEMORY (like the .NET version does)
# and only committing to the DB at the very end
# this will resolve problems where we can't find instances, or use syntactic sugar
(globalIdentifier, templateKey, template) = self.get_instance_sugar_for_elem(elem)
print ("globalIdentifier = " + str(globalIdentifier) + ", templateKey = " + str(templateKey))
if template is None:
# no sugar
pass
customTagName = None
if "customTagName" in elem:
customTagName = elem["customTagName"]
self.yamlIds[customTagName] = elem
print("registering customTagName '" + customTagName + "'")
if "registerForTemplate" in elem:
if elem["registerForTemplate"] == True:
self.templates[self.manager.expand_entity_references(globalIdentifier)] = elem
print("registering elem for template")
classId = None
classIndex = None
if template is None and elemParent is not None:
template = elemParent
if template is not None:
if "instance" in template:
classId = Guid.parse(self.manager.expand_entity_references(template["instance"]))
else:
for key in self.yamlIds:
if key in template:
classId = Guid.parse(self.manager.expand_entity_references(template[key]))
break
if "index" in template:
classIndex = template["index"]
instanceId = None
creatorKey = None
if "instance" in elem:
if "templateOnly" in elem and elem["templateOnly"] == True:
# instanceId = None
instanceId = Guid.parse(self.manager.expand_entity_references(elem["instance"]))
else:
instanceId = Guid.parse(self.manager.expand_entity_references(elem["instance"]))
creatorKey = 'instance'
else:
for key in self.yamlIds:
if key in elem:
instanceId = Guid.parse(self.manager.expand_entity_references(elem[key]))
creatorKey = key
break
index = None
if "index" in elem:
index = elem["index"]
# else:
# print("index not found for element")
# print(elem)
if classId is None and instanceId is not None:
#!!! HACK HACK HACK !!!
classId = instanceId
if classIndex is None and index is None:
classIndex = 1
index = 1
print("WARNING: class hack used for instanceId " + instanceId.get_value() + " ( index " + str(classIndex) + "$" + str(index) + ")")
if instanceId is not None:
self.manager.add_instance(classId, instanceId, classIndex, index)
if creatorKey == 'class': #FIXME: remove this special-case behavior
print("creating class " + instanceId.get_value())
# # assign relationship `Instance.for Class`
# self.manager.assign_relationship(instanceId, Guid.parse('494D5A6D04BE477B8763E3F57D0DD8C8'), Guid.parse('B9C9B9B7AD8A4CBDAA6BE05784630B6B'))
# # assign relationship `Class.has Instance`
# self.manager.assign_relationship(Guid.parse('B9C9B9B7AD8A4CBDAA6BE05784630B6B'), Guid.parse('7EB41D3C2AE9488483A4E59441BCAEFB'), instanceId)
if "instances" in elem:
classInsts = elem["instances"]
for inst in classInsts:
self.load_instance(inst, elem)
else:
print("creating instance " + instanceId.get_value() + " (of class " + classId.get_value() + ")")
# # assign relationship `Instance.for Class`
# self.manager.assign_relationship(instanceId, Guid.parse('494D5A6D04BE477B8763E3F57D0DD8C8'), classId)
# # assign relationship `Class.has Instance`
# self.manager.assign_relationship(classId, Guid.parse('7EB41D3C2AE9488483A4E59441BCAEFB'), instanceId)
# self.manager.create_instance_of(classId, instanceId, classIndex, index)
if templateKey is not None:
if template is not None:
print("using template (" + templateKey + ")")
#print(template)
self.load_instance_with_template(elem, template)
else:
#print("ignoring '" + templateKey + "'")
pass
def load_instances_from_file(self, filename):
print("loading instances from " + filename)
import yaml
with open(filename, 'r') as file:
content = yaml.safe_load_all(file)
for doc in content:
for elem in doc:
if "entityDefinitions" in elem:
continue
elif "library" in elem:
self.load_library(filename, elem)
elif "tenant" in elem:
self.load_tenant(filename, elem)
else:
raise MochaSyntaxError("neither 'library' nor 'tenant' top level element found")
def load_library(self, filename, elem):
if "instances" in elem:
instances = elem["instances"]
for elem1 in instances:
if "instance" in elem1:
# this is an instance definition (no sugar), so just load it
self.load_instance(elem1)
else:
# this is a syntactic sugar definition, so store it for later use
elem1["from_file_name"] = filename
self._instances_awaiting_sugar.append(elem1)
def load_tenant(self, filename, elem):
pass

View File

@ -0,0 +1,4 @@
from .LibraryParser import LibraryParser
from .JSONLibraryParser import JSONLibraryParser
from .XMLLibraryParser import XMLLibraryParser
from .YAMLLibraryParser import YAMLLibraryParser

View File

@ -0,0 +1,7 @@
class PathMapping:
def __init__(self, source_pattern : str, destination_path : str):
self.source_pattern = source_pattern
self.destination_path = destination_path

View File

@ -13,6 +13,7 @@ class WebPage (WebControl):
self.__style_sheet = WebStyleSheet() self.__style_sheet = WebStyleSheet()
self.add(self.__head) self.add(self.__head)
self.__head.add(self.__style_sheet) self.__head.add(self.__style_sheet)
self.add(self.__body) self.add(self.__body)

View File

@ -65,6 +65,11 @@ class WebRequestHandler(BaseHTTPRequestHandler):
path = self.url.path.split('/') path = self.url.path.split('/')
path = path[1:] path = path[1:]
mapping = self.server._server.find_path_mapping(path)
if mapping is not None:
self.respond_with_content(200, "OK", mapping.content_type, mapping.content)
return
if (len(path) == 1 and path[0] == ""): if (len(path) == 1 and path[0] == ""):
self.respond_with_redirect("/" + default_tenant_name) self.respond_with_redirect("/" + default_tenant_name)
return return

View File

@ -1,4 +1,5 @@
from .WebRequestHandler import WebRequestHandler from . import WebRequestHandler, PathMapping
from ..oms import Oms from ..oms import Oms
class WebServer(): class WebServer():
@ -10,10 +11,31 @@ class WebServer():
""" """
self.__tup = endpoint self.__tup = endpoint
self._oms = oms self._oms = oms
self.path_mappings = [ ]
def match_path_pattern(self, source_pattern, path_format):
vars = [ ]
if source_pattern == path_format:
return (True, vars)
# TODO: add intelligent path parsing from PHP/Phast
# from framework.web import WebPathParser... (e.g.)
return (False, vars)
def find_path_mapping(self, path_format : str):
for mapping in self.path_mappings:
(success, vars) = self.match_path_pattern(mapping.source_pattern, path_format)
if success:
for varname in vars:
mapping.set_property_value(varname, vars[varname])
return mapping
return None
def serve_forever(self): def serve_forever(self):
from http.server import HTTPServer from http.server import HTTPServer
server = HTTPServer(self.__tup, WebRequestHandler) server = HTTPServer(self.__tup, WebRequestHandler)
server._oms = self._oms server._oms = self._oms
server._server = self
server.serve_forever() server.serve_forever()

View File

@ -0,0 +1,12 @@
from .xml import XmlTag
class WebStyleSheetReference (XmlTag):
def __init__(self, path):
XmlTag.__init__(self, 'link')
self.add_attribute("rel", "stylesheet")
self.add_attribute("type", "text/css")
self.add_attribute("href", path)
def has_content_or_controls(self):
return False

View File

@ -0,0 +1,7 @@
from .PathMapping import PathMapping
from .WebPage import WebPage
from .WebRequestHandler import WebRequestHandler
from .WebScript import WebScript
from .WebServer import WebServer
from .WebStyleSheet import WebStyleSheet
from .WebStyleSheetReference import WebStyleSheetReference

View File

@ -0,0 +1,46 @@
from ...oms.memory import MemoryOms
from ..WebServer import WebServer
from ..PathMapping import PathMapping
class ServerManager:
def __init__(self):
self._servers = [ ]
def add_server_config(self, filename):
import yaml
with open(filename, 'r') as file:
content = yaml.safe_load_all(file)
for doc in content:
for d in doc:
for s in d["server"]:
if not "port" in s:
print("error: 'port' not defined for server definition")
return
port = s["port"]
print("[ INFO ]: running server on port " + str(port))
oms = MemoryOms()
svr = WebServer(("127.0.0.1", port), oms)
if "pathMappings" in s:
pathMappings = s["pathMappings"]
for pathMapping in pathMappings:
svr.path_mappings.append(PathMapping(pathMapping["source"], pathMapping["destination"]))
print("[ INFO ]: map path '" + pathMapping["destination"] + "' to file path '" + pathMapping["source"] + "'")
self._servers.append(svr)
def start(self):
import threading
for server in self._servers:
t = threading.Thread(target = self._serve, args = [ server ])
t.start()
def _serve(self, server):
server.serve_forever()

View File

@ -0,0 +1 @@
from .ServerManager import ServerManager

View File

@ -2,6 +2,8 @@ from ...WebScript import WebScript
from ...WebPage import WebPage from ...WebPage import WebPage
from ....oms import Oms from ....oms import Oms
from ...WebStyleSheetReference import WebStyleSheetReference
class BaseWebPage (WebPage): class BaseWebPage (WebPage):
def __init__(self): def __init__(self):
@ -11,6 +13,7 @@ class BaseWebPage (WebPage):
def on_initializing(self): def on_initializing(self):
self.get_style_sheet().add_rule('table', { 'width': '100%' }) self.get_style_sheet().add_rule('table', { 'width': '100%' })
self.get_head().add(self.get_client_config_script()) self.get_head().add(self.get_client_config_script())
self.get_head().add(WebStyleSheetReference("/madi/asset/ui-html/2024.03.09/css/main.css"))
def get_oms(self): def get_oms(self):
return self._oms return self._oms