Export Handlers
Introduction
An Export Handler defines logic for handling dependencies that are part of a Submodel.
Submodels often contain dependencies. Dependencies are objects that exist outside the submodel container, but are required for objects within the container.
For each class of object that could be a dependency (such as Global Tables, Groups, Lists, etc.) there is one corresponding Export Handler. If there is no handler for a given type, that type of object cannot be a dependency of a submodel.
This topic describes how handlers work in detail. After reading this topic, you should have enough information to understand the existing export handlers and create your own custom export handlers if needed.
The Export Handler API
An Export Handler is any node that implements the Export Handler API. In addition, all handlers must be found at the path:
MAIN:/project/exec/globals/ExportHandlersA Handler must implement expose the API through the
function_s() command.
The two best ways to do that are as follows:
- Create a node with Object data. Add executable nodes to the
eventfunctionsattribute. This is how the built-in handlers are implemented. - Create a custom class that inherits SimpleDataType. Implement the API methods as
members of the class. Then use
bindCallback()to expose the API methods. See SimpleDataType: Binding Callbacks for more information.
The Export Handler API defined by the ExportHandler class. The class is contained in the file
FlexSimInstallDir/program/system/include/SubmodelExport.h.
This topic will cover the API in two parts: the Basic API and the Advanced API.
The Basic API
The following methods are part of the basic API.
enumerate() - optional
The enumerate() function is called once for each handler. This method is called without arguments.
It should return an array of objects that are potential dependencies for this handler.
For example, the Global Tables handler returns an array of all Global Tables in the model.
findReferences(treenode exportObj) - optional
The findReferences() method returns an array of objects that are referenced by the given export object. This method
is called once for each object within the exported container as well as the container itself.
The goal is to examine the exported object and return the set of dependencies that it references,
or "soft" dependencies.
For example, the Groups handler returns the set of Groups that contain the given object.
Objects returned by this method will be exported by default in the dependencies table.
Objects returned by this method are also considered "clients" of the dependency.
findDependencies(treenode exportObj) - optional
The findDependencies() method returns an array of objects that are dependencies of the given export object. This method
is called once for each object within the exported container as well as the container itself.
The goal is to examine the exported object and return the set of dependencies that it requires,
or "hard" dependencies. You should only return an object from this method if the exported object cannot
function without the dependency. For example, the Warehousing Visualization handler returns the visualzation used
by the given object, if that given object is a Rack. Without the visualization, the Rack would be invisible.
Objects returns by this method are exported and are locked in the dependencies table.
Objects returned by this method are also considered "clients" of the dependency.
enumerateRequirementOptions() - required
The enumerateRequirementOptions() method must return an array with at least one requirement node.
The user can select which requirement to use when importing the dependency.
The requirement is only evaluated if the dependency already exists in the model during import.
If so, the requirement is evaluated. If it returns a 1, the existing object is considered compatible with the imported object,
and so the existing object is used. If it returns a 0, the existing object is considered incompatible. The user must accept
that the existing object will be overwritten by the imported object to continue with the import.
A requirement node must meet the following criteria:
- The requirement node must exist at the same path in the import model as in the export model.
- The requirement node must evaluate to the integer value 1 or 0. The following values are passed to the requirement node:
param(1)- the existing object in the importing model.param(2)- the imported object. Important - during import, only the tree structure of an object is loaded. The C++ class is not bound on the imported object. This means that calling class methods on the imported object is not possible. In general, you should determine compatibility by comparing tree nodes and values.param(3)- the handler node.param(4)- the export reference node.param(5)- the imported model that contains the imported object. Only the tree of the model is loaded, so while you can investigate the tree of the loaded model, you can't call class methods on any Object Data Type object.
- The requirement node must have a subnode called "hint" with text describing the requirement.
- The requirement node must have a subnode called "name" with text that shows as the requirement name.
Note that a requirement will rarely compare node for node and value for value. Many object have node values that don't create a conflict. For example, a Process Flow object contains nodes for the current zoom and pan of the view. Whether these values match is irrelevant to whether the existing object is compatible with the imported object. The Process Flow requirement skips these nodes (among many others) when comparing the existing and imported objects.
findByName(string name, treenode parent, treenode exportRef) - required
The findByName() method is called during import. Given the name, this method should return
an existing object with that name, if it exists. For example, the Global Table handler will find any global table
with the given name and return it. If this method does not return an object, the import algorithm assumes that
the imported object does not exist and will be added to the model. Otherwise, the import algorithm will run the
requirement function between the existing object (as found by this method) and the imported object.
The first parameter is the name of the import object. The second parameter is the parent of the export object (if any). The third is the export reference node itself.
add(treenode imported, treenode parent, treenode exportRef) - required
The add() method is called during import when an object does not already exist in the model.
This method should add the imported object to the model in the correct location. For example,
the Global Table handler will add the imported global table to the Tools/GlobalTables folder.
Usually, you can do this by moving the imported node into the correct location in the model tree.
The first parameter is the imported object. The second parameter is the parent of the export object (if any). The third is the export reference node itself.
use(treenode existing, treenode imported, treenode parent, treenode exportRef) - required
The use() method is called during import when an object already exists in the model and the requirement
function has returned a 1, indicating that the existing object is compatible with the imported object.
This function should modify the existing object so that it works with the imported submodel. For example,
the MTBF/MTTR handler has a set of members. The handler's use() method will add the imported object's
members to the existing object's members list.
The first parameter is the existing object. The second parameter is the imported object. The third parameter is the parent of the export object (if any). The fourth is the export reference node itself.
replace(treenode existing, treenode imported, treenode parent, treenode exportRef) - required
The replace() method is called during import when an object already exists in the model and the requirement
function has returned a 0, indicating that the existing object is incompatible with the imported object.
This method should replace the existing object with the imported object.
In a sense, this is opposite of the use() method. For example, the MTBF/MTTR handler's replace()
saves the existing object's members list, then replaces the existing object with the imported object,
and finally restores the saved members list to the new object.
The first parameter is the existing object. The second parameter is the imported object. The third parameter is the parent of the export object (if any). The fourth is the export reference node itself.
getTypeDisplay() - required
The getTypeDisplay() method should return a string that describes the type of object handled by this export handler.
This string is shown in the Export Dependencies dialog when the user is selecting which dependencies to include in the submodel.
For example, the Global Tables handler returns "Global Table".
getDependencyDisplay(treenode object) - required
The getDependencyDisplay() method should return a string that describes the specific dependency represented by the given object.
This string is shown in the Export Dependencies dialog when the user is selecting which dependencies to include in the submodel.
For example, the Global Tables handler returns the name of the given global table.
The first parameter is the object to be displayed.
The Advanced API
The following methods are part of the advanced API.
getParentObject(treenode exportObj) - optional
The getParentObject() method should return the parent object of the given export object. You only need
to implement this method if the handler deals with a component of a larger object and that component is referenced
directly by the exported container or objects within the container. For example, the Address Scheme handler
returns the Storage System as its parent object. This has the following effects:
- Parent objects are always imported before their components.
- Parent objects are passed in to the
add(),use(), andreplace()methods of their components. - It is possible that adding the parent object will add the component by coincidence.
In this case, the component's
useAdded()method will be called.
The first parameter is the exported object meaning the component handled by this handler.
getSubHandlers() - optional
The getSubHandlers() method returns an array of additional handlers.
Subhandlers create a similar relationship as parent objects. However, subhandlers are used
when the best way to find a component is to search the parent object rather than searching the
objects in the container. For example, the List handler returns a List Field handler.
This has the following effects:
- Whenever an object of the parent type is found, it calls all its subhandlers passing in the found object, rather than the objects in the container.
- The
enumerate()method of the subhandler is never called. - The
getParentObject()method of a subhandler must be implemented. - The
getSuperHandler()method of a subhandler must be implemented.
If a handler returns any nodes from this method, those nodes are considered subhandlers. Simply returning a parent object does not make the handler a subhandler. Use a subhandler when the component you need to find is easiest to discover by searching the parent object, rather than searching the objects in the container.
findPrerequisites(treenode dependency) - optional
The findPrerequisites() method is called when a new dependency is found.
This method should return an array of all other dependencies that must be imported
and present before this dependency can be imported. For example, the Slot Label handler
returns the Storage System and the Color Palette that it references, if any.
Note that a parent object always counts as a prerequisite.
The first parameter is the dependency object.
getPrerequisiteHandler(treenode preReq) - optional
The getPrerequisiteHandler() method is called when a new prerequisite is reported
either from the findPrerequisites() method or from the getParentObject() method.
This method should return the handler that handles the given prerequisite object. For example, the Slot Label handler
returns the Storage System handler when given a Storage System prerequisite.
The first parameter is the prerequisite object.
getSuperHandler() - optional
The getSuperHandler() method should return the handler that is the parent of this subhandler.
This method must be implemented if the handler is a subhandler.
saveExtraData(treenode exportObj, treenode exportRef) - optional
The saveExtraData() method is called for each export reference when the model is saved.
You can use this function to save extra data about the exported reference. For example, all tool
handlers use this method to save their heirarchy in the toolbox so that the import can ensure that
those same toolbox folders exist.
All the extra data should be saved on a subnode of the export reference. To create and access that subnode, you can use code like this:
treenode obj = param(1);
treenode ref = param(2);
treenode myExtraData = function_s(ref, "getExtraData", "myExtraData");
// now use myExtraData to save whatever extra data you need
You only need to implement this method if there is extra metadata about the object that is not contained within the object itself.
The first parameter is the exported object. The second parameter is the export reference node.
processExtraData(treenode existing, treenode exportRef) - optional
The processExtraData() method is called during import after the object has been properly imported.
As with the saveExtraData() method, you can retrieve the extra data subnode from the export reference:
treenode obj = param(1);
treenode ref = param(2);
treenode myExtraData = function_s(ref, "getExtraData", "myExtraData");
// now use myExtraData as needed
You only need to implement this method if you also implemented saveExtraData().
The first parameter is the existing object. The second parameter is the export reference node.
useAdded(treenode existing, treenode imported, treenode parent, treenode exportRef) - optional
The useAdded() method is called if the import algorithm detects that a component (such as a list field)
was added by coincidence when the parent object (such as a list) was added.
The first parameter is the existing object. The second parameter is the imported object. The third parameter is the parent of the export object (if any). The fourth is the export reference node itself.
updateProperties(treenode templateDef, Array dependencies, treenode object, treenode container) - optional
The updateProperties() method is called when the submodel is saved.
When creating a new instance of the submodel, certain properties are applied after
creating a copy of the container. This method allows the handler to specify what the initial value
of that property should be. For example, the Ports handler sets the InObjects, OutObjects, and CenterObjects
properties on the templateDef object.
The first parameter is the template definition. This is the object that acts as the template object when the model is imported. The second parameter is an array of dependencies. These dependencies are the objects from the same handler that list the current object as a client. The third parameter is the object. The fourth parameter is the container.
canIgnoreDirectPointer(treenode dependency, treenode pointingNode) - optional
The canIgnoreDirectPointer() method is called when the submodel is saved. It is an error for a node to point directly
at a node within the exported container. Normally, such a pointer would cause an exception.
However, sometimes that pointer is reset or ignored by the import algorithm. In that case, it is safe to ignore the pointer.
Note that joined nodes are already excluded from this check. Only one-way pointers are considered.