This article is the first in a series of hands-on articles which will show you how to
access the resources within your process model by scripting.
It is Friday afternoon and the boss is becoming very anxious about the current
process modeling project for Monkey Zoo (a very important client). He wants to
know how many diagrams are now in the Monkey Zoo process model, he wants to
know their names, descriptions, versions, the date last changed and by whom. He
also wants to know the number of BPMN objects contained in each diagram in
order to get an idea of the complexity of each diagram. He wants this list on his
desk by 8.30AM Monday in time for a meeting with the client
You could use a pen and paper and transcribe the the information from the 150 or
more diagrams in the project or you could write a script to do it.
These days, when the topic of process model interchange arises, the discussion usually swings
around to interoperability via XPDL, BPEL or some other file format based on an Xml encoding.
There is another more dynamic way of interacting with a process model. It requires that the process
modeler has a DOM (Document Object Model) and a scripting language which makes it possible to
reference a process model's objects directly without requiring Xml transcoded files. ActiveModeler
Avantage is such a modeler, as well as providing XPDL 2.0 file interchange, it also provides a
.NET DOM for BPMN and the various UI library functions within the modeler.
Scripting comes into its own when you need to create add-hoc reports and lists, add your own
dialogs/user interfaces and generally interact with or contribute to the process model dynamically.
Lua for process modeling
Lua is a modern scripting language that keeps turning up in the oddest range of places, from being
used as the logic engine in WOW (the phenomenally successful World of Warcraft), to being
embedded in network appliances, used in DNA sequencing applications and in NASA. Lua offers
distinct advantages over Javascript, Python, Perl and others when used as an embedded scripting
engine, importantly it has a small memory footprint and high execution speed and an elegant
simplicity. Lua is a natural for process modeling applications.
The Avantage Lua plugin packages the Lua.NET scripting engine based on LuaInterface. For a very
interesting (but somewhat technical) description of the implementation see LuaInterface: Scripting the .NET CLR with Lua from the Journal of Universal Computer Science.
The Avantage Lua plugin itself gives you full access to both the Avantage APIs and all of the .NET
framework classes from within your script. To make it easy, a high level wrapper is provided by the
plugin which simplifies getting at the process model and some of the Avantage UI libraries.
Avantage, a Lua script is executed in its own dedicated process thread, so it has minimal impact on
the performance of the main UI thread. This allows Lua scripts to perform lengthy time consuming
work leaving the user free to continue with other things. Scripts can work with Windows.Forms, UserControls and any other .NET objects.

Lua is pretty easy to learn so this article does not try to teach you the language, for that you should
read the online book Programming in Lua by Roberto Ierusalimschy. What you do need to know is that in order to use a .NET class from Lua you must import it first. This is like a using statement in C#.
MessageBox = luanet.System.Windows.Forms.MessageBox
Color = luanet.System.Drawing.Color
File = luanet.System.IO.File
Path = luanet.System.IO.Path
ArrayList = luanet.System.Collections.ArrayList
To create a object is easy once it has been imported
myColor = Color(Color.Yellow)
myPath = Path(“C:\temp”)
The final thing to keep in mind is that unlike VB.NET, C#, Javascript, Java et al, you reference a
method with the “:” operator not “.”.
Lua DOM (Document Object Model)
In order to save you from any possible confusion, I can tell you right now that the Wikipedia
definition of a DOM is not accurate. It assumes that DOMs are exclusively the province of web
browsers, HTML and XML. This is not so. To be accurate, a DOM is a set of programming APIs targetting a specific application document domain. Naturally, for a web browser the domain is HTML however for a process modeler the domain is a process model. The namespace domain of the Lua DOM is KT.AM.Lua.DOM

ProcessModel
The process model DOM consists of two stacks. The base process model stack (known as BPTree) is composed of BPObjects. BPObjects can be arranged in hierarchies (BPTrees) and they take care of saving themselves (persisting) to files and other important stuff like providing a transactioning mechanism so that actions can be undone. BPObjects are the raw DNA from which a full process modeling stack is built.
The BPMN 1.0 stack (BPMNTree) inherits from the BPObjects stack (BPTree). The KT.AM.Core.Resources.ProcessModel.BPMN namespace is a complete implementation of BPMN 1.0 as a programming API. There is very little that you can't do with a BPMN process model in this way.

You can access the complete BPMN 1.0 process model API documentation [ here ] as a .chm
(Microsoft Compiled Help) file.
Scripts can contribute menu items to any Avantage popup context menu. Popup context menus
contain actions that are applicable to the type of object selected. For example, a Diagram object has
different context menus to a Lane object's context menus. The different menus appear because the objects are different. We refer to an object's menus as object contributions. A script could for example popup a “Project report...” menu item when the user right-clicks on a Project object. When the user selects that item then a script handler is executed to perform the action which is advertised by the menu contribution.
Tables
A script usually needs to output stuff in order to be useful. A TableEditor object is provided
which provides a grid/table style window rendering of the output from your script. This is useful for
lists, reports etc. For text and integer columns, there are some simplified access functions. This is sufficient for the vast majority of output. For richer content tables the underlying XPTable object itself must be directly accessed (in order to provide features like image cells, drop-down lists, checkbox cells and
more).
The Task
1. Create a new popup menu item which will appear when the user right-clicks on any Project
item in the Avantage workspace.
2. Assign a Lua script function delegate to handle the action when the user clicks on this menu.
3. Create a Grid/table style report in response to this click. For each diagram in the selected
project output the information that the boss wants to see.
We are going to create an ObjectPopup menu. This kind of menu is tied to a specific class of .NET
object, and whenever an object of that type is selected in the Avantage workspace then the menu
item is displayed.
ObjectPopup(string ClassPath, string name, string menuId, string description)
In our example we want the menu to appear only when project objects are selected. The fully
enumerated interface name of a Project object is KT.AM.Core.Resources.Workspace.IProject
so this is the first parameter string of the ObjectPopup() function. The second parameter is the text
of the menu that the user will see, the menuID is a unique identifier which identifies your action and
the description is a short description of the action suitable for display in tooltip text.
theMenu = ObjectPopup("KT.AM.Core.Resources.Workspace.IProject", "Project report",
"Project_report_id", "Report on all diagrams in this project")
This function returns a LuaContributor object. LuaContributors enclose an Action object through
which you can control the menu itself. We will set an icon image for the menu. This is done by the
SetIcon() function:
SetIcon(string ImageRegistry, string ImageName)
The imageregistry is the name of one of several image registries in Avantage, we will use the
“BPIcons” registry in this example (the BPIcons registry contains the standard images for the
objects which appear in the BPTree). The imagename is the name of the image within the registry.
We will choose the “Property” icon. (Read more about the ImageRegistries.)
theMenu.Action:SetIcon("BPIcons", "Property")
Next, we need to tell the menu the name of the script function that is to be executed when the user
clicks the menu. The OnRun() function is used for this purpose. Its parameter is the name of the
Lua function to be run.
handler = theMenu.Action:OnRun(ClickThisRun)
The menu handler function receives two parameters. The args object contains the arguments, one of
the properties of the args object is the selected object in the navigator that is the subject of the
menu's action. In the code snippet below, a function called DoReport() is called.
function ClickThisRun(sender,args)
DoReport(data.selectedObject)
end

Creating the report
The script will display its output in a window we call a TableEditor which is a typical grid
composed of columns and rows. The image below shows what they look like (the yellow window).

Create the TableEditor object
theReport = NewReport(string title, Color backgroundColor, Color gridlineColor)
The title parameter is the name of the report which is to be displayed on the tab. When you Save the
report, this name is used for the file name. You can also specify the background color of the window and the color of the grid lines.
In order to prevent the window from being needlessly updated while it is being filled, it is usual to
Freeze() it. When the filling process is complete you Unfreeze() it and the window is drawn.
Add columns to it
When you initially create a TableEditor it doesn't have any columns – you have to define them in
your script. For simple tables, the AddTextColumn() function can be used to do this.
Currently the TableEditor object only directly allows you to create TextColumns. If you
need to create columns other than TextColumns then you need to do a little bit more work.
You can create the full repertoire of column types (ButtonColumn, CheckBoxColumn,
ImageColumn,NumberColumn, ProgressBarColumn,ComboBoxColumn DateTimeColumn
and ColorColumn) by accessing the TableEditor.Table property. This provides you with a
reference to an instance of an XPTable object. You can download the API documentation
for XPTable from here XPTable - .NET ListView meets Java's JTable.
column = report:AddTextColumn(string column_title, int width, bool sorted)
Adding rows
A Row must be created for each line of detail in your report. To create a new row use the
TableEditor:AddRow() function. AddRow returns a XPTable.Models.Row object.
ewrow = report:AddRow()
Setting the contents of a row cell
To set the contents of a cell you call the TableEditor:SetCell() function
TableEditor:SetCell(XPTable.Models.Row theRow, int columnIndex, string theValue)
report:SetCellText(newrow, 2, theDiagram.Name)
report:SetCell(newrow, 5, theDiagram.ID)
report:SetCell(newrow, 7, theDiagram.Version)
report:SetCell(newrow, 8, theDiagram.Author)
report:SetCell(newrow, 4, theDiagram.ModificationDate:ToShortDateString ())
report:SetCell(newrow, 3, theDiagram.CreationDate:ToShortDateString ())
Notice that the last two SetCell() calls do some .NET date formatting. Because the
BPD_Diagram.ModificationDate and BPD_Diagram.ModificationDate properties are actually
.NET DateTime objects in the process model we can call their ToShortDateString() methods (by
using the “:” operator)
Saving a report to a file
Filled TableEditors automatically enable the File menu Save as... menu action. The table is saved
in CSV (Comma Separated Values) format which is widely supported.
Getting a list of files to process
Now that you have a rough idea of the mechanics of creating a menu and EditorTables it is time to
get to work. If you remember, the menu Run handler receives a reference to object the user selected
(clicked on). Because we are only contributing to project objects, the selected object will always be
a project file.
What's in a project file? Essentially a project file contains a list of the files which are registered in
that project. These are usually .model, .organization, .categories, .txt, etc. The files we are interested
in are .model files because these contain BPMN process diagrams. A project file also has a
ParentFolder property which provides the directory information for the project. A project's Files[]
array property contains a list of the File objects it manages. File objects represent files in the
filesystem, they have name, URI and other attribute information. So, all we have to do is get the selected project when the Run is called and then examine every File object in its Files[] array looking for files with the .model extension. For each .model file found we will open it and then access its BPMN process model to get the necessary information for the report.
function DumpProject(report, theProject)
if (theProject ~= nil) then
row = report:AddRow()
report:SetCell(row, 0, theProject.Name)
files = theProject.RootFolder.Files
filecnt = files.Length
for fileno = 0, filecnt-1, 1 do
file = files[fileno]
if (file ~= nil) then
if (string.find(file.URI, ".model") ~= nil) then
newrow = report:AddRow()
report:SetCell(newrow, 1, Path:GetFileName(file.URI))
report:SetCell(newrow, 4, File:GetLastWriteTimeUtc(file.URI):ToString())
report:SetCell(newrow, 3, File:GetCreationTimeUtc(file.URI):ToString())
tree = OpenModel(file)
if (tree ~= nil) then
AnalyseModel(tree, report)
tree = nil
end
end
end
end
end
end
Getting into a process model
Given a .model file we can open it by calling the OpenModel() function. This function returns a
BPMNTree object (you can read more about them in the .chm file you downloaded earlier).
A process model may contain multiple diagram objects, they are contained in the tree's
BPMNTree.BPMN_Diagrams collection (of type BPD_Diagram).
The script function here iterates the BPMN_Diagrams collection.
function AnalyseModel(tree, report)
for i = 0, tree.BPMN_Diagrams.Count,1 do
theDiagram = At(tree.BPMN_Diagrams, i)
if theDiagram ~= nil then
newrow = report:AddRow()
report:SetCellText(newrow, 2, theDiagram)
--- report:SetCellText(newrow, 2, theDiagram.Name)
report:SetCell(newrow, 6, ChildItemsCount(theDiagram))
report:SetCell(newrow, 5, theDiagram.ID)
report:SetCell(newrow, 7, theDiagram.Version)
report:SetCell(newrow, 8, theDiagram.Author)
report:SetCell(newrow, 4, theDiagram.ModificationDate:ToShortDateString ())
report:SetCell(newrow, 3, theDiagram.CreationDate:ToShortDateString ())
report:SetCell(newrow, 9, theDiagram.Description)
end
end
end
If you remember from earlier, BPObjects are hierarchical. BPObjects have parents and children, and
their children can have children etc. In our example, the boss wants to know how many items are in
each diagram, so we have to write some code to do this. That is what the ChildItemsCount()
function does.
function ChildItemsCount(diagram)
recursive = true
list = ArrayList()
CollectBPDChildren(list, diagram, recursive)
return list.Count
end
This function is a Lua wrapper for calling one of the workhorse functions of the process model –
CollectBPDChildren().
CollectBPDChildren(ArrayList list, BPObject obj, bool recursive)
CollectBPDChildren returns a flattened list of children of an object. If the recursive parameter is set
true then the children of the children etc are included in the list. In our example here we set
recursive true because we want to know the count of all objects contained directly and indirectly by
the diagram object.
Wrapping it up
This article really only just touches the surface of what you can do with the Lua scripting language,
BPMN and Avantage. In the next article I will explain how to script custom properties and introduce more of the DOM to you.
Appendix Image registry names
The Action:SetIcon() function allows you to decorate a menu item with a pre-defined icon. Below
is a list of the registries and their image names.
“StandardIcons”
New|Open|Save|SaveAll|SaveAs|Cut|Copy|Paste|Delete|Properties|Undo|Redo|Preview|Print|Search|ReSearch|Help|ZoomIn|ZoomOut|Back|Forward|Favorites|AddToFavorites|
Stop|Refresh|Home|Edit|Tools|Tiles|Icons|List|Details|Pane|Culture|Languages|
History|Mail|Parent|FolderProperties|FullScreen|Camera|Calculator|LayoutContent|
BookOpen
“BPIcons”
Model|ModelSelected|BPFolder|BPFolderSelected|AsisMap|AsisMapSelected|ToBeMap|
ToBeMapSelected|AutomatedMap|AutomatedMapSelected|AsisActivity|
AsisActivitySelected|ToBeActivity|ToBeActivitySelected|AutomatedActivity|
AutomatedActivitySelected|Candidate|CandidateSelected|Group|GroupSelected|
AsisMapSeries|AsisMapSeriesSelected|Error|ErrorSelected|ToBeMapSeries|
ToBeMapSeriesSelected|ComplexModel|ComplexModelSelected|FormItem|
FormItemSelected|Rules|RulesSelected|Attachment|AttachmentSelected|Log|
LogSelected|Department|DepartmentSelected|Role|RoleSelected|Product|
ProductSelected|StartEvent|StartEventSelected|IntermediateEvent|
IntermeditateEventSelected|EndEvent|EndEventSelected|Task|TaskSelected|
MeasurePoint|MeasurePointSelected|Property|PropertySelected|Gateway|
GatewaySelected|Diagram|DiagramSelected|Pool|PoolSelected|Lane|LaneSelected|
TriggerNone|TriggerNoneSelected|TriggerMessage|TriggerMessageSelected|
TriggerTimer|TriggerTimerSelected|TriggerException|TriggerExceptionSelected|
TriggerCancel|TriggerCancelSelected|TriggerCompensation|
TriggerCompensationSelected|TriggerRule|TriggerRuleSelected|TriggerLink|
TriggerLinkSelected|TriggerMultiple|TriggerMultipleSelected|TriggerTerminate|
TriggerTerminateSelected|GatewayAND|GatewayANDSelected|GatewayOR|
GatewayORSelected|GatewayComplex|GatewayComplexSelected|GatewayXOREvent|
GatewayXOREventSelected|GatewayXOR|GatewayXORSelected|MetricatedPropertyActive|
MetricatedPropertyActiveSelected|MetricatedPropertyInactive|
MetricatedPropertyInactiveSelected|SequenceFlow|SequenceFlowSelected|
ConditionalFlow|ConditionalFlowSelected|MessageFlow|MessageFlowSelected|
Association|DirectionalAssociation|ExceptionFlow|CompensationAssociation|
PropertySet|PropertySetSelected|BPMNProcess|BPMNProcessSelected|BPMNSubProcess|
BPMNSubProcessSelected|DataObjectArtifact|DataObjectArtifactSelected|
AnnotationArtifact|AnnotationArtifactSelected|GroupArtifact|
GroupArtifactSelected
Complete listing of the script
MessageBox = luanet.System.Windows.Forms.MessageBox
Color = luanet.System.Drawing.Color
File = luanet.System.IO.File
Path = luanet.System.IO.Path
ArrayList = luanet.System.Collections.ArrayList
tablenumber = 0
function ChildItemsCount(diagram)
recursive = true
list = ArrayList()
CollectBPDChildren(list, diagram, recursive)
return list.Count
end
function AnalyseModel(tree, report)
for i = 0, tree.BPMN_Diagrams.Count,1 do
theDiagram = At(tree.BPMN_Diagrams, i)
if theDiagram ~= nil then
newrow = report:AddRow()
report:SetCellText(newrow, 2, theDiagram)
--- report:SetCellText(newrow, 2, theDiagram.Name)
report:SetCell(newrow, 6, ChildItemsCount(theDiagram))
report:SetCell(newrow, 5, theDiagram.ID)
report:SetCell(newrow, 7, theDiagram.Version)
report:SetCell(newrow, 8, theDiagram.Author)
report:SetCell(newrow, 4, theDiagram.ModificationDate:ToShortDateString ())
report:SetCell(newrow, 3, theDiagram.CreationDate:ToShortDateString ())
report:SetCell(newrow, 9, theDiagram.Description)
end
end
end
function DumpProject(report, theProject)
if (theProject ~= nil) then
row = report:AddRow()
report:SetCell(row, 0, theProject.Name)
files = theProject.RootFolder.Files
filecnt = files.Length
for fileno = 0, filecnt-1, 1 do
file = files[fileno]
if (file ~= nil) then
if (string.find(file.URI, ".model") ~= nil) then
newrow = report:AddRow()
report:SetCell(newrow, 1, Path:GetFileName(file.URI))
report:SetCell(newrow, 4, File:GetLastWriteTimeUtc(file.URI):ToString())
report:SetCell(newrow, 3, File:GetCreationTimeUtc(file.URI):ToString())
tree = OpenModel(file)
if (tree ~= nil) then
AnalyseModel(tree, report)
tree = nil
end
end
end
end
end
end
--- This func creates table editor window.
function DoReport(selectedProject)
tablenumber = tablenumber + 1
theReport = NewReport("Project Report"..tablenumber, Color.LemonChiffon, Color.Gray);
theReport:Freeze()
notsorted = false
sorted = true
projcol = theReport:AddTextColumn("Project", 120, notsorted)
modelcol = theReport:AddTextColumn("Model", 120, notsorted)
diagramcol = theReport:AddTextColumn("Diagram", 160, notsorted)
createcol = theReport:AddTextColumn("Created", 80, notsorted)
modcol = theReport:AddTextColumn("Modified", 80, notsorted)
idcol = theReport:AddTextColumn("ID", 50, notsorted)
activitycountcol = theReport:AddTextColumn("# Items", 60, notsorted)
versioncol = theReport:AddTextColumn("Version", 80, notsorted)
authorcol = theReport:AddTextColumn("Author", 60, notsorted)
descrcol = theReport:AddTextColumn("Description", 150, notsorted)
DumpProject(theReport, selectedProject)
theReport:Unfreeze()
end
function ReportMenuClicked(sender, args)
if (args ~= nil) then
DoReport(args.selectedObject)
end
end
function UnloadContributions(sender, args)
MessageBox:Show("The project reporting thread is now terminated")
Terminate()
end
--- Create an object popup menu
os.setlocale("en_US.UTF-8")
clickThis = ObjectPopup("KT.AM.Core.Resources.Workspace.IProject", "Project report", "Clickthis_1", "Report on all diagrams in project")
clickThis.Action:SetIcon("BPIcons", "Property");
handler = clickThis.Action:OnRun(ReportMenuClicked);
unload = ObjectPopup("KT.AM.Core.Resources.Workspace.IProject", "Unload project reporting", "report_unload", "Remove reporting menus")
unload.Action:OnRun(UnloadContributions)
--- This script thread will now sleep forever if there are contributions defined. To force unloading when you have contributions
-- you must call Terminate().