﻿//Copyright (c) Fabasoft Austria GmbH, Linz, Austria, 2016-2025
//
//All rights reserved. All hardware and software names used are registered
//trade names and/or registered trademarks of the respective manufacturers.
//
//The user of this computer program acknowledges that the above copyright
//notice, which constitutes the Universal Copyright Convention, will be attached
//at the position in the function of the computer program which the author has
//deemed to sufficiently express the reservation of copyright. It is prohibited for
//customers, users and/or third parties to remove, modify or move this
//copyright notice.

// Required Parameters
@activity=NULL; // Modify so that 'NULL' is replaced by one COO-Address (ex. "@activity=COO.1.2.3.4;")

// Optional Parameters
@verbose=false; // Don't change unless explicitly guided to by Fabasoft Support!
@allversions=true;

// Internal Parameters
@specattribs = [#objname, #objclass, #objaclobj];
@skipattribs = [#FSCPDFGEN@1.1001:pdfcontent, #FSCPDFGEN@1.1001:pdfcontentprint];
@revision=strreplace(strreplace(strreplace("$Rev: 31776 $", "Rev:"), "$"), " ");

// Prepare output file
@jsondoc = coort.CreateDictionary();
@jsondoc.format = "com.fabasoft.support.objectinfo.json"; 
@jsondoc.tool = "DumpProcessInfo [svn-" + @revision + "]";
@jsondoc.errors = [];
@jsondoc.objects = coort.CreateDictionary();

// If there is no activity, assume we are running inside a bulk operation and dump the current object
@batchmode = false;
@objects = [];
if(@activity == NULL) {
  try {
    @activity = cooobj;	
    @batchmode = true;
  }
  catch (@ex) {
    @jsonerror = coort.CreateDictionary();
    @jsonerror.SetEntry("message", "No objects to dump!");
    @jsondoc.SetEntryValue("errors", @jsondoc.GetEntryValueCount("errors"), @jsonerror);
  }
}

// Parse the process
@processes = [];
if(@activity != NULL) {
  @objects *= []; // Do not pre-fill with @activity, that would break the tree evaluation!

  if(@activity.HasClass(#COOWF@1.1:InstanceElement)) {
    @processes *= @activity.COOWF@1.1:actinstprocinst;
  } else if(@activity.HasClass(#COOWF@1.1:ProcessInstance)) {
    @processes *= @activity;
  } else if(@activity.HasClass(#COOWF@1.1:ProcessDefinition)) {
    @processes *= @activity;
  } else {
    // Check if the object has COOWF@1.1:workflow available
    @class = @activity.objclass;
    do {
      if(#COOWF@1.1:workflow in @class.classattributes) {
        @processes *= @activity.COOWF@1.1:workflow;
        break;
      }
      @class = @class.classsuperclass;
    } while(@class != null);
  }
  
  @objects *= @processes;
}

for(@process : @processes) {
  @start = [];
  if(@process.HasClass(#COOWF@1.1:ProcessInstance)) {
    @start = @process.COOWF@1.1:procinststart;
    @objects *= @process.COOWF@1.1:procinstobject;
  } else if(@process.HasClass(#COOWF@1.1:ProcessDefinition)) {
    @start = @process.COOWF@1.1:procdefstart;
  }

  for(@sactivity : @start) {
    @act = coort.CreateDictionary();
    @act.value = @sactivity;
    @act.actidx = 0;
    @act.end = [];
    @actstack = [];

    do {
      // Collect activity
      @objects *= @act.value;
      if(@act.value.HasClass(#COOWF@1.1:ActivityInstance)) {
        @objects *= @act.value.COOWF@1.1:actinstobjects;
      } else if(@act.value.HasClass(#COOWF@1.1:ActivityDefReference)) {
        @objects *= @act.value.COOWF@1.1:actdefrefactdef;
      }

      // Collect child activities
      try {
        // Initialize new state
        @nact = coort.CreateDictionary();
        @nact.actidx = 0;
        @nact.end = @act.end;
        
        // Class specific initialization
        if(@act.value.HasClass(#COOWF@1.1:ConditionStartInstance)) {
          if(@act.actidx < COUNT(@act.value.COOWF@1.1:instthennext)) {
            @nact.value = @act.value.COOWF@1.1:instthennext[@act.actidx++];
          } else if(@act.actidx < (COUNT(@act.value.COOWF@1.1:instthennext) + COUNT(@act.value.COOWF@1.1:instelsenext))) {
            @nact.value = @act.value.COOWF@1.1:instelsenext[@act.actidx++ - COUNT(@act.value.COOWF@1.1:instthennext)];
          } else if(@act.actidx <= (COUNT(@act.value.COOWF@1.1:instthennext) + COUNT(@act.value.COOWF@1.1:instelsenext))) {
            @nact.value = @act.value.COOWF@1.1:instcondend;
            @act.actidx++;
          }
          @nact.end += @act.value.COOWF@1.1:instcondend;
        } else if(@act.value.HasClass(#COOWF@1.1:CaseStartInstance)) {
          if(@act.HasEntry("childidx") && @act.childidx < COUNT(@act.children)) {
            @nact.value = @act.children[@act.childidx++];
          } else {
            if(@act.actidx < COUNT(@act.value.COOWF@1.1:instcasenext)) {
              @act.childidx = 1;
              @act.children = @act.value.COOWF@1.1:instcasenext[@act.actidx].COOWF@1.1:actinstnext;
              @nact.value = @act.children[0];
              @act.actidx++;
            } else if(@act.actidx <= COUNT(@act.value.COOWF@1.1:instcasenext)) {
              @nact.value = @act.value.COOWF@1.1:instcaseend;
              @act.actidx++;
            }
          }
          @nact.end += @act.value.COOWF@1.1:instcaseend;
        } else if(@act.value.HasClass(#COOWF@1.1:InstanceElement)) {
          if(@act.actidx < COUNT(@act.value.COOWF@1.1:actinstnext)) {
            @nact.value = @act.value.COOWF@1.1:actinstnext[@act.actidx++];
          }
        } else if(@act.value.HasClass(#COOWF@1.1:ConditionStartDefinition)) {
          if(@act.actidx < COUNT(@act.value.COOWF@1.1:defthennext)) {
            @nact.value = @act.value.COOWF@1.1:defthennext[@act.actidx++];
          } else if(@act.actidx < (COUNT(@act.value.COOWF@1.1:defthennext) + COUNT(@act.value.COOWF@1.1:defelsenext))) {
            @nact.value = @act.value.COOWF@1.1:defelsenext[@act.actidx++ - COUNT(@act.value.COOWF@1.1:defthennext)];
          } else if(@act.actidx <= (COUNT(@act.value.COOWF@1.1:defthennext) + COUNT(@act.value.COOWF@1.1:defelsenext))) {
            @nact.value = @act.value.COOWF@1.1:defcondend;
            @act.actidx++;
          }
          @nact.end += @act.value.COOWF@1.1:defcondend;
        } else if(@act.value.HasClass(#COOWF@1.1:CaseStartDefinition)) {
          if(@act.HasEntry("childidx") && @act.childidx < COUNT(@act.children)) {
            @nact.value = @act.children[@act.childidx++];
          } else {
            if(@act.actidx < COUNT(@act.value.COOWF@1.1:defcasenext)) {
              @act.childidx = 1;
              @act.children = @act.value.COOWF@1.1:defcasenext[@act.actidx].COOWF@1.1:actdefnext;
              @nact.value = @act.children[0];
              @act.actidx++;
            } else if(@act.actidx <= COUNT(@act.value.COOWF@1.1:defcasenext)) {
              @nact.value = @act.value.COOWF@1.1:defcaseend;
              @act.actidx++;
            }
          }
          @nact.end += @act.value.COOWF@1.1:defcaseend;
        } else if(@act.value.HasClass(#COOWF@1.1:DefinitionElement)) {
          if(@act.actidx < COUNT(@act.value.COOWF@1.1:actdefnext)) {
            @nact.value = @act.value.COOWF@1.1:actdefnext[@act.actidx++];
          }
        }
        
        // Push current context
        if(@nact.HasEntry("value")) {
          if(@nact.value not in @act.end && @nact.value not in @objects) {
            @actstack += @act;
            @act = @nact;
          }
        } else if(COUNT(@actstack) > 0) {
          // Pop from stack
          @act = @actstack[-1];
          DELETE(@actstack, COUNT(@actstack) - 1);
        } else {
          @act = null;
        }
      }
      catch (@ex) {
        @jsonerror = coort.CreateDictionary();
        @jsonerror.SetEntry("message", coort.GetErrorText(@ex));
        @jsonerror.SetEntry("statement", coort.GetErrorStatement(@ex));
        @jsonerror.SetEntry("line", coort.GetErrorLine(@ex));
        @jsonerror.SetEntry("column", coort.GetErrorColumn(@ex));
        @jsonerror.SetEntry("callstack", coort.GetErrorCallStack(@ex));

        @jsondoc.SetEntryValue("errors", @jsondoc.GetEntryValueCount("errors"), @jsonerror);

        @act.actidx++;
      }
    } while(@act != null);
  }
}

// Build object dumps
for(@object : @objects) {
  try {
    @versions = [coort.GetCurrentDateTime(coouser)];
    if(@allversions) {
      try {
        for(@version : @object.objversions) {
          @versions += @version.verschangedat;
        }
      }
      catch (@ex) { 
        coort.Trace("Error: Could not read all versions, only tracing current version!");
      }
    } else {
      coort.Trace("Info: Only tracing current version!");
    }

    @jsonobj = coort.CreateDictionary();
    @jsonobj.current = @object.objactversnr;
    @jsonobj.versions = coort.CreateDictionary();

    for(@versdate : @versions) {
      @jsonver = coort.CreateDictionary();;
    
      // Trace a version
      @vobject = @object.ObjectGetVersion(@versdate);

      // Build list of all object attributes
      @class = @vobject.objclass;
      @classattribs = @specattribs;
      do {
        @classattribs *= @class.classattributes;
        @class = @class.classsuperclass;
      } while(@class != null);
      @classattribs -= @skipattribs;
      
      // Add any userforms attributes if defined
      if(coort.GetObject("COO.1.1001.1.179597").HasClass(#AttributeObjectDef) && @vobject.COOTC@1.1001:objcategory.HasClass(Object)) {
        @classattribs *= @vobject.COOTC@1.1001:objcategory.GetAttribute(cootx, coort.GetObject("COO.1.1001.1.179597"));
      }

      // Trace object attributes
      for(@attr : @classattribs) {
        if(@vobject.HasAttributeValue(cootx, @attr) || @verbose) {
          @jsonname = strjoin([@attr.component.reference, "@", @attr.component.objdomain.domainmajorid, ".", @attr.component.objdomain.domainminorid, ":", @attr.reference, "/", @attr.attrtype.component.reference, "@", @attr.attrtype.component.objdomain.domainmajorid, ".", @attr.attrtype.component.objdomain.domainminorid, ":", @attr.attrtype.reference]);

          try {
            @count = @vobject.GetAttributeValueCount(cootx, @attr);
            for(@idx = 0; @idx < @count; @idx++) {
              if(@attr.HasClass(#AttributeObjectDef)) {
                @value = @vobject.GetAttributeValue(cootx, @attr, @idx);
                if(@value != null) {
                  @jsonref = coort.CreateDictionary();
                  @jsonref.SetEntry("objaddress", @value.GetAddress());

                  try {
                    @jsonref.SetEntry("objclass", strjoin([@value.objclass.component.reference, "@", @value.objclass.component.objdomain.domainmajorid, ".", @value.objclass.component.objdomain.domainminorid, ":", @value.objclass.reference]));
                  }
                  catch (@ex) {
                    @jsonref.SetEntry("objclass", @value.objclass.GetAddress());
                  }

                  if(@value.HasClass(#ComponentObject)) {
                    try {
                      if(@value.component != null && @value.reference != null) {
                        @jsonref.SetEntry("objname", strjoin([@value.component.reference, "@", @value.component.objdomain.domainmajorid, ".", @value.component.objdomain.domainminorid, ":", @value.reference]));
                      } else {
                        @jsonref.SetEntry("objname", @value.objname);
                      }
                    }
                    catch (@ex) {
                      @jsonref.SetEntry("objname", strjoin(["Unknown: ", coort.GetErrorText(@ex)]));
                    }
                  } else {
                    try {
                      @jsonref.SetEntry("objname", @value.objname);
                    }
                    catch (@ex) {
                      @jsonref.SetEntry("objname", strjoin(["Unknown: ", coort.GetErrorText(@ex)]));
                    }
                  }
                  
                  @jsonver.SetEntryValue(@jsonname, @idx, @jsonref);
                } else {
                  @jsonver.SetEntryValue(@jsonname, @idx, "<null>");
                }
              } else if(@attr.attrtype in [#CONTENT]) {
                @contval = @vobject.GetAttributeValue(cootx, @attr, @idx);
                try {
                  @jsonver.SetEntryValue(@jsonname, @idx, strjoin([@contval.GetID(), " [", @contval.GetHash(), "]"]));
                }
                catch (@ex) {
                  @jsonver.SetEntryValue(@jsonname, @idx, STRING(@contval.GetID()));
                }
              } else if(@attr.HasClass(#AttributeEnumDef)) {
                @enumval = @vobject.GetAttributeValue(cootx, @attr, @idx);
                try {
                  @jsonver.SetEntryValue(@jsonname, @idx, strjoin([@attr.attrtype.typeenumvalues[typeenumval==@enumval].typeenumref, " (", @enumval, ")"]));
                }
                catch (@ex) {
                  @jsonver.SetEntryValue(@jsonname, @idx, STRING(@enumval));
                }
              } else if(@attr.HasClass(#AttributeAggregateDef)) {
                // Trace aggregate children
                @agg = coort.CreateDictionary();
                @agg.validx = 0;
                @agg.attridx = 0;
                @agg.valcount = 0;
                @agg.value = @vobject.GetAttributeValue(cootx, @attr, @idx);
                @agg.attribs = @attr.attrtype.typecompattrs;
                @agg.pidx = @idx;
                @agg.pcount = @count;
                @agg.json = coort.CreateDictionary();
                @agg.jsonname = "";
                @aggstack = [];

                do {
                  // Trace child values
                  @aggattr = @agg.attribs[@agg.attridx];
                  if(@agg.value.HasAttributeValue(@aggattr) || @verbose) {
                    @agg.jsonname = strjoin([@aggattr.component.reference, "@", @aggattr.component.objdomain.domainmajorid, ".", @aggattr.component.objdomain.domainminorid, ":", @aggattr.reference, "/", @aggattr.attrtype.component.reference, "@", @aggattr.attrtype.component.objdomain.domainmajorid, ".", @aggattr.attrtype.component.objdomain.domainminorid, ":", @aggattr.attrtype.reference]);

                    try {
                      @agg.valcount = @agg.value.GetAttributeValueCount(@aggattr);
                      for(;@agg.validx < @agg.valcount; @agg.validx++) {
                        if(@aggattr.HasClass(#AttributeAggregateDef)) {
                          // Push current context
                          @aggstack += @agg;
                          
                          @pagg = @agg;
                          @agg = coort.CreateDictionary();
                          @agg.validx = 0;
                          @agg.attridx = 0;
                          @agg.valcount = -1;
                          @agg.value = @pagg.value.GetAttributeValue(@aggattr, @pagg.validx);
                          @agg.attribs = @aggattr.attrtype.typecompattrs;
                          @agg.pidx = @pagg.validx++;
                          @agg.pcount = @pagg.valcount;
                          @agg.json = coort.CreateDictionary();
                          @agg.jsonname = "";

                          // Interrupt the current loop
                          break;
                        } else if(@aggattr.HasClass(#AttributeObjectDef)) {
                          @value = @agg.value.GetAttributeValue(@aggattr, @agg.validx);
                          if(@value != null) {
                            @jsonref = coort.CreateDictionary();
                            @jsonref.SetEntry("objaddress", @value.GetAddress());

                            try {
                              @jsonref.SetEntry("objclass", strjoin([@value.objclass.component.reference, "@", @value.objclass.component.objdomain.domainmajorid, ".", @value.objclass.component.objdomain.domainminorid, ":", @value.objclass.reference]));
                            }
                            catch (@ex) {
                              @jsonref.SetEntry("objclass", @value.objclass.GetAddress());
                            }
                  
                            if(@value.HasClass(#ComponentObject)) {
                              try {
                                if(@value.component != null && @value.reference != null) {
                                  @jsonref.SetEntry("objname", strjoin([@value.component.reference, "@", @value.component.objdomain.domainmajorid, ".", @value.component.objdomain.domainminorid, ":", @value.reference]));
                                } else {
                                  @jsonref.SetEntry("objname", @value.objname);
                                }
                              }
                              catch (@ex) {
                                @jsonref.SetEntry("objname", strjoin(["Unknown: ", coort.GetErrorText(@ex)]));
                              }
                            } else {
                              try {
                                @jsonref.SetEntry("objname", @value.objname);
                              }
                              catch (@ex) {
                                @jsonref.SetEntry("objname", strjoin(["Unknown: ", coort.GetErrorText(@ex)]));
                              }
                            }

                            @agg.json.SetEntryValue(@agg.jsonname, @agg.validx, @jsonref);
                          } else {
                            @agg.json.SetEntryValue(@agg.jsonname, @agg.validx, "<null>");
                          }
                        } else if(@aggattr.HasClass(#AttributeEnumDef)) {
                          @enumval = @agg.value.GetAttributeValue(@aggattr, @agg.validx);
                          try {
                            @agg.json.SetEntryValue(@agg.jsonname, @agg.validx, strjoin([@aggattr.attrtype.typeenumvalues[typeenumval==@enumval].typeenumref, " (", @enumval, ")"]));
                          }
                          catch (@ex) {
                            @agg.json.SetEntryValue(@agg.jsonname, @agg.validx, STRING(@enumval));
                          }
                        } else if(@aggattr.attrtype in [#CONTENT]) {
                          @contval = @agg.value.GetAttributeValue(@aggattr, @agg.validx);
                          try {
                            @agg.json.SetEntryValue(@agg.jsonname, @agg.validx, strjoin([@contval.GetID(), " [", @contval.GetHash(), "]"]));
                          }
                          catch (@ex) {
                            @agg.json.SetEntryValue(@agg.jsonname, @agg.validx, STRING(@contval.GetID()));
                          }
                        } else if(@aggattr.attrtype in [#DICTIONARY]) {
                          @agg.json.SetEntryValue(@agg.jsonname, @agg.validx, coouser.FSCEXPEXT@1.1001:Value2JSON(@agg.value.GetAttributeValue(@aggattr, @agg.validx)));
                        } else {
                          if(COUNT(@aggattr.COOATTREDIT@1.1:attrrepresentation[COOATTREDIT@1.1:uiaction==COOATTREDIT@1.1:CTRLPassword]) > 0) {
                            @agg.json.SetEntryValue(@agg.jsonname, @agg.validx, "****");
                          } else {
                            @agg.json.SetEntryValue(@agg.jsonname, @agg.validx, @agg.value.GetAttributeValue(@aggattr, @agg.validx));
                          }
                        }
                      }

                      // Reset index if we reached the end
                      if(@agg.validx == @agg.valcount) {
                        @agg.validx = 0;
                        @agg.valcount = 0;
                        @agg.attridx++;
                      }
                    }
                    catch (@ex) {
                      @jsonerror = coort.CreateDictionary();
                      @jsonerror.SetEntry("message", coort.GetErrorText(@ex));
                      @jsonerror.SetEntry("statement", coort.GetErrorStatement(@ex));
                      @jsonerror.SetEntry("line", coort.GetErrorLine(@ex));
                      @jsonerror.SetEntry("column", coort.GetErrorColumn(@ex));
                      @jsonerror.SetEntry("callstack", coort.GetErrorCallStack(@ex));

                      @jsondoc.SetEntryValue("errors", @jsondoc.GetEntryValueCount("errors"), @jsonerror);
                    }
                  } else {
                    @agg.attridx++;
                  }

                  // Pop from stack
                  if(@agg.attridx == COUNT(@agg.attribs) && COUNT(@aggstack) > 0) {
                    @pagg = @aggstack[-1];
                    @pagg.json.SetEntryValue(@pagg.jsonname, @agg.pidx, @agg.json);

                    @agg = @pagg;
                    DELETE(@aggstack, COUNT(@aggstack) - 1);
                  }
                } while(@agg.attridx < COUNT(@agg.attribs));

                @jsonver.SetEntryValue(@jsonname, @idx, @agg.json);
              } else if(@attr.attrtype in [#DICTIONARY]) {
                @jsonver.SetEntryValue(@jsonname, @idx, coouser.FSCEXPEXT@1.1001:Value2JSON(@vobject.GetAttributeValue(cootx, @attr, @idx)));
              } else {
                if(COUNT(@attr.COOATTREDIT@1.1:attrrepresentation[COOATTREDIT@1.1:uiaction==COOATTREDIT@1.1:CTRLPassword]) > 0) {
                  @jsonver.SetEntryValue(@jsonname, @idx, "****");
                } else {
                  @jsonver.SetEntryValue(@jsonname, @idx, @vobject.GetAttributeValue(cootx, @attr, @idx));
                }
              }
            }
          }
          catch (@ex) {
            @jsonerror = coort.CreateDictionary();
            @jsonerror.SetEntry("message", coort.GetErrorText(@ex));
            @jsonerror.SetEntry("statement", coort.GetErrorStatement(@ex));
            @jsonerror.SetEntry("line", coort.GetErrorLine(@ex));
            @jsonerror.SetEntry("column", coort.GetErrorColumn(@ex));
            @jsonerror.SetEntry("callstack", coort.GetErrorCallStack(@ex));

            @jsondoc.SetEntryValue("errors", @jsondoc.GetEntryValueCount("errors"), @jsonerror);
          }
        }
      }

      @jsonobj.versions.SetEntry(STRING(@vobject.objactversnr), @jsonver);
    }

    @jsondoc.objects.SetEntry(@object.GetAddress(), @jsonobj);
  }
  catch (@ex) {
    @jsonerror = coort.CreateDictionary();
    @jsonerror.SetEntry("message", coort.GetErrorText(@ex));
    @jsonerror.SetEntry("statement", coort.GetErrorStatement(@ex));
    @jsonerror.SetEntry("line", coort.GetErrorLine(@ex));
    @jsonerror.SetEntry("column", coort.GetErrorColumn(@ex));
    @jsonerror.SetEntry("callstack", coort.GetErrorCallStack(@ex));

    @jsondoc.SetEntryValue("errors", @jsondoc.GetEntryValueCount("errors"), @jsonerror);
  }
}

// Serialize to JSON
@jsoncontent = coort.CreateContent();
if(coort.GetObject("COO.1.1001.1.322609").HasClass(#Action)) {
  coouser.CallAction(cootx, "FSCEXPEXT@1.1001:Value2JSONContent", @jsondoc, &@jsoncontent);
} else {
  @jsontext = coouser.FSCEXPEXT@1.1001:Value2JSON(@jsondoc);
  @jsoncontent.SetContent(cootx, 1, 65001, @jsontext);
}

@message = "";
if(@batchmode) {
  try new transaction {
    @note = #NOTE@1.1:NoteObject.ObjectCreate();
    @note.content = { contcontent: @jsoncontent, contextension: "tro" };
    @note.objname = strhead(strjoin(["ProcessInfo dump of ", strjoin(@activity.GetAddress(), ", ")]), 254);

    cooroot.ObjectLock(true, true);
    cooroot.objchildren += @note;

    @message = strjoin(["Process dump created and saved in '", @note.objname, "' (", @note.GetAddress(), ") at the root desktop!"]);
  }
  catch(@ex) {
    @message = strjoin(["Could not create note object on desktop, please see the file '", @jsoncontent.GetFile("object.tro", true), "' for the contents of the dump!"]);
  }
} else {
  @path = strjoin(["./", strhead(strjoin(["ProcessInfo dump of ", strjoin(@activity.GetAddress(), ", ")]), 250), ".tro"]);
  @path = @jsoncontent.GetFile(@path, false);

  @message = strjoin(["Created process dump of '", strjoin(@activity.GetAddress(), ", "), "' at '", @path, "', please upload this file to the service desk!"]);
}
@message;