In Part 1 of my tutorial on ProM plug-in development, we went over the basics: objects, plug-ins, and visualizers. In this part, we look into loading and saving objects, how to let users specify configuration, and how to make our plug-ins provide more information during execution. In the remainder of this post, I assume you have read (or at least are familiar with the contents of) Part 1.
Loading/Saving Objects
Loading and saving, or in ProM terms, importing and exporting, objects is as most things in ProM handled by plug-ins.
Importing
Strictly speaking, we have to implement a particular interface (org.processmining.framework.abstractplugins.ImportPlugin), but ProM provides an abstract base class, so we can do it even simpler. A simple plug-in for importing Person objects (from Part 1) can be seen below:
[java] package org.processmining.plugins.helloworld;import org.processmining.contexts.uitopia.annotations.*;
import org.processmining.framework.abstractplugins.*;
import org.processmining.framework.plugin.*;
import org.processmining.framework.plugin.annotations.*;
@Plugin(name = “Import Person”,
parameterLabels = { “Filename” },
returnLabels = { “Person” },
returnTypes = { Person.class })
@UIImportPlugin(description = “Person”,
extensions = { “person” })
public class PersonImportPlugin extends AbstractImportPlugin {
@Override
protected Person importFromStream(final PluginContext context,
final InputStream input,
final String filename,
final long fileSizeInBytes) {
try {
context.getFutureResult(0).setLabel(“Person imported from ” + filename);
} catch (final Throwable _) {
// Don’t care if this fails
}
Parson result = new Person();
// Fill in object from input
return result;
}
}
[/java]
The Plugin annotation (ll. 8-11) should be familiar by now. The UIImportPlugin (ll. 12-13) describes the files imported by this plug-in. We need to specify a description for the files and the supported extension(s).
The main content of the plug-in is the method importFromStream, which must have the shown signature. The method is given an InputStream, which can be used for loading. It can be wrapped in any of the higher level streams or readers. The plug-in is also given the name of the file opened and the total size. This information is useful for providing progress as shown later.
The first part of the plug-in makes sure that the name of the object shown in ProM is a bit more meaningful than just “Person”; here, we embed the name of the file in the name of the object. This is done using the code in line 21. We get into more details about this code later.
Finally, we create a new Person (l. 25) and populate it (l. 26; not shown here), and return it (l. 27).
Exporting
Exporting is even simpler than importing. We just need to implement a plug-in which takes an object and a File as parameters and has no return value.
[java] package org.processmining.plugins.helloworld;import java.io.*;
import org.processmining.framework.plugin.*;
import org.processmining.framework.plugin.annotations.*;
import org.processmining.contexts.uitopia.annotations.*;
@Plugin(name = “Export Person”,
parameterLabels = { “Person”, “File” },
returnLabels = {},
returnTypes = {})
@UIExportPlugin(description = “Person file”,
extension = “person”)
public class ExportPersonPlugin {
@UITopiaVariant(affiliation = “University of Life”,
author = “Britney J. Spears”,
email = “britney@westergaard.eu”)
@PluginVariant(requiredParameterLabels = { 0, 1 })
public void export(PluginContext context,
Person person,
File file) throws IOException {
FileWriter writer = new FileWriter(file);
PrintWriter pwriter = new PrintWriter(writer);
pwriter.print(person.getFirst());
pwriter.print(‘ ‘);
pwriter.println(person.getLast());
pwriter.close();
}
}
[/java]
We note that nothing is special here, except we take a File as the second parameter, and we have added an UIExportPlugin annotation describing the output tile and extension (ll. 12-13).
Graphical Configuration Specification
ProM uses a particular user interface style. Alas, this is not pluggable Look and Feel based, but instead uses specially configured widgets. The basic user interface is based on SlickerBox and is extended by the ProM package Widgets. Despite old code in ProM not adhering to this, you always want to only use widgets from either of these sources. You never want to use a JPanel and manually set the colors or any other old-fashioned hacks performed in ProM. If a particular widget you need is not available, add it to the Widgets package and use it, never create your own local widget.
All configuration should be done using the ProMPropertiesPanel, which provides a generic view for properties. It can show any widget, and comes pre-populated with some basic ones. Here is a very simple example of the populate method missing from Part 1:
[java] package org.processmining.plugins.helloworld;import org.processmining.framework.plugin.annotations.*;
import org.processmining.framework.plugin.*;
import org.processmining.contexts.uitopia.*;
import org.processmining.contexts.uitopia.annotations.*;
import org.processmining.framework.util.ui.widgets.*;
@Plugin(name = “Procreate”,
parameterLabels = { “Father”, “Mother”, “Procreation Configuration” },
returnLabels = { “Child” },
returnTypes = { Person.class })
public class ProcreatePlugin {
// Actual plug-in methods from Part 1 go here
public static ProcreationConfiguration populate(final UIPluginContext context,
final ProcreationConfiguration config) {
ProMPropertiesPanel panel = new ProMPropertiesPanel(“Configure Procreation”);
ProMTextField name = panel.addTextField(“Name”, config.getName());
final InteractionResult interactionResult = context.showConfiguration(“Setup Procreation”, panel);
if (interactionResult == InteractionResult.FINISHED ||
interactionResult == InteractionResult.CONTINUE ||
interactionResult == InteractionResult.NEXT) {
config.setName(name.getText());
return config;
}
return null;
}
}
[/java]
The ProMPropertiesPanel also has methods addCheckBox and addCombobox as well as a generic addProperty, which also takes a JComponent as parameter. The populate method returns null if the configuration was cancelled.
The setup, population, and reading of results from the ProMProperties panel is fairly standard and shall not be discussed. The code to display it, line 20, is ProM-specific, though. Here, we just show a single-page widget, which is easiest done as here. We give a title and a component to show, and get an InteractionResult back. This indicates which button the user used to confirm or cancel the action. Here we set the field in the configuration if the response is any of the positive clicks. If the user clicks cancel, we do not modify the configuration.
The populate method returns the given configuration if the user clicks ok and null if cancel is selected. This is not used in the code in Part 1, but could be used to stop processing if a user clicks cancel.
If a plug-in has more settings than sensible fit on a single page, the Widgets packet also provides a ProMWizard which should be used. We get back to that in a later part.
Interaction with ProM User-interface
All plug-ins are given a PluginContext (or a UIPluginContext) as the first parameter. This gives access to a lot of functionality for communicating status to users and otherwise interacting with ProM. We have already seen two means of interacting with ProM and the user: getFutureResult in the import example, and showConfiguration from the configuration example.
PluginContexts come in different kinds. The basic one is PluginContext, which makes no assumptions about the environment of ProM. ProM actually comes in different versions, including a command line version, the standard graphical version, and a version for running on clusters/grids, and depending on the context required by plug-ins they are available on all or only some of these versions. The UIPluginContext assumes a graphical front-end and should only be used if really required. This is the reason for splitting our plug-ins into two variants: one doing the computation without any assumptions about the presence of a graphical user-interface and one for populating the configuration from the graphical user-interface.
The PluginContext, gives us access to logging, reporting progress, and descriptors for the results. We also get access to all plug-ins, objects, and connections of the ProM workspace, but we save these more advanced topics for later. If we have a graphical context (UIPluginContext), we also get access to the showConfiguration method shown previously.
Logging
Often plug-ins may want to provide logging, either to report where they are in the execution or to report errors. The PluginContext provides a simple means of logging. This should not be used for extensive reporting, but is useful as a quick and dirty mechanism. For more advanced logging, I recommend using the logging facility of the Widgets package, which is described here.
Logging is performed using the log method of the PluginContext. It takes either a message and an optional MessageLevel or an exception. Consider this modified version of the worker method of the ProcreatePlugin:
[java] package org.processmining.plugins.helloworld;import org.processmining.framework.plugin.annotations.*;
import org.processmining.framework.plugin.*;
import org.processmining.contexts.uitopia.*;
import org.processmining.contexts.uitopia.annotations.*;
@Plugin(name = “Procreate”,
parameterLabels = { “Father”, “Mother”, “Procreation Configuration” },
returnLabels = { “Child” },
returnTypes = { Person.class })
public class ProcreatePlugin {
@UITopiaVariant(affiliation = “University of Life”,
author = “Britney J. Spears”,
email = “britney@westergaard.eu”,
uiLabel = UITopiaVariant.USEPLUGIN)
@PluginVariant(requiredParameterLabels = { 0, 1, 2 })
public static Person procreate(final PluginContext context,
final Person father,
final Person mother,
final ProcreationConfiguration config) {
context.log(“Creating new Person”, MessageLevel.NORMAL);
Person child = new Person();
child.setAge(0);
if (config == null) {
context.log(“No configuration given!”, MessageLevel.ERROR);
return null;
}
if (“Xtina”.equalsIgnoreCase(config.getName())) {
context.log(“Person has stupid name!”, MessageLevel.WARNING);
}
try {
child.setName(new Name(config.getName(), father.getLast()));
context.log(“New person set up!”, MessageLevel.DEBUG);
} catch (Exception e) {
context.log(e);
return null;
}
context.log(“About to successfully return.”, MessageLevel.TEST);
return child;
}
// Variant without configuration and populate method go here
}
[/java]
Here we see logging at 5 different levels (ll. 22, 26, 30, 34, and 39) and logging of an exception (l. 36); if the level is omitted, it corresponds to MessageLevel.NORMAL. We note that this only assumes a PluginContext and hence works on all instances of ProM, including non-graphical ones.
Progress
Plug-ins can also report their progress. They can call context.getProgress(), which returns a Progress object which provides various ways of providing progress information. This works without requiring a graphical context. Plug-ins should provide such information to not leave the user hanging. Progress can be provided in two ways: a plug-in can say it does not know how long it will take, or it can increment a progress counter. Good behavior is to initially set the so-called indeterminate progress and as soon as the real progress is known use that instead. Here is an example of the procreate plug-in with progress information:
[java] package org.processmining.plugins.helloworld;import org.processmining.framework.plugin.annotations.*;
import org.processmining.framework.plugin.*;
import org.processmining.contexts.uitopia.*;
import org.processmining.contexts.uitopia.annotations.*;
@Plugin(name = “Procreate”,
parameterLabels = { “Father”, “Mother”, “Procreation Configuration” },
returnLabels = { “Child” },
returnTypes = { Person.class })
public class ProcreatePlugin {
@UITopiaVariant(affiliation = “University of Life”,
author = “Britney J. Spears”,
email = “britney@westergaard.eu”,
uiLabel = UITopiaVariant.USEPLUGIN)
@PluginVariant(requiredParameterLabels = { 0, 1, 2 })
public static Person procreate(final PluginContext context,
final Person father,
final Person mother,
final ProcreationConfiguration config) {
context.setMaximum(5);
context.setValue(1);
Person child = new Person();
context.setValue(2);
child.setAge(0);
context.setValue(3);
child.setName(new Name(config.getName(), father.getLast()));
context.setValue(5);
return child;
}
@UITopiaVariant(affiliation = “University of Life”,
author = “Britney J. Spears”,
email = “britney@westergaard.eu”,
uiLabel = UITopiaVariant.USEPLUGIN)
@PluginVariant(requiredParameterLabels = { 0, 1 })
public static Person procreate(final UIPluginContext context,
final Person father,
final Person mother) {
context.getProgress().setIndeterminate();
ProcreationConfiguration config = new ProcreationConfiguration();
populate(context, config);
return procreate(context, father, mother, config);
}
// populate method goes here
}
[/java]
In line 41 we state that we do not know the progress currently. This makes sense, as the user may leave the configuration open for a long time.
In line 22 we say that progress goes to 5. Here, we have 5 steps of the creation. We may also have more steps and some steps may take longer than others (and hence correspond to more steps like the naming in the example). Alternatively, we can specify 100 and directly provide percentages. In line 23 we immediately increment the counter to 1. We need to set it to something positive to have the progress bar show up in ProM. We then increment it as we progress (ll. 25, 27, and 29). We assume the last step takes twice as long as the others and hence increment the progress by two here.
The Progress also gives access to reading the maximum value, the current value, and to set and read a minimum value. The class also has a method for setting a caption, but this does not work currently, so don’t use that.
Finally, the Progress can be cancelled programmatically and it is possible for plug-ins to check whether it has been cancelled. Normally a plug-in is just terminated if cancel is selected, but they can set the handlesCancel property of the Plugin annotation to true and check the value of isCancelled themselves to handle cancellation (more) gracefully.
Result Handling
Each of the results of a plug-in is described using a Future, which is a representation of the result before it is ready. Plug-ins have access to these futures by invoking context.getFutureResult with the index of the result.
The most important method of the Future is the setLevel method illustrated in the import example. It makes it possible to change the name of objects before they are returned to the ProM workspace.
Futures also have a method cancel, which takes a boolean. If execution of a plug-in needs to terminate unexpectedly (e.g., by the user pressing cancel during configuration), it makes sense to cancel the first result. A better way of implementing the graphical variant of the procreation example is as follows:
[java] package org.processmining.plugins.helloworld;import org.processmining.framework.plugin.annotations.*;
import org.processmining.framework.plugin.*;
import org.processmining.contexts.uitopia.*;
import org.processmining.contexts.uitopia.annotations.*;
@Plugin(name = “Procreate”,
parameterLabels = { “Father”, “Mother”, “Procreation Configuration” },
returnLabels = { “Child” },
returnTypes = { Person.class })
public class ProcreatePlugin {
@UITopiaVariant(affiliation = “University of Life”,
author = “Britney J. Spears”,
email = “britney@westergaard.eu”,
uiLabel = UITopiaVariant.USEPLUGIN)
@PluginVariant(requiredParameterLabels = { 0, 1 })
public static Person procreate(final UIPluginContext context,
final Person father,
final Person mother) {
context.getProgress().setIndeterminate();
ProcreationConfiguration config = new ProcreationConfiguration();
config = populate(context, config);
if (config == null) {
context.getFutureResult(0).cancel(true);
return null;
}
return procreate(context, father, mother, config);
}
// Variant with configuration and populate method go here
}
[/java]
Now we use the return value of populate in line 23, and check if it was null in line 24. If so, we cancel the first (and here only) result (l. 25) and return null. Otherwise, we progress as normally.
Conclusion
We have seen how to integrate closer with ProM 6. We have seen how to load and save objects and simple ways of configuring plug-ins using the Widgets packet and the graphical version of ProM. We have seen various uses of the ProMContext to provide better feed-back to users.
In the next installment, we shall look into interacting with ProM’s provided object manager, how to make multi-step configurations, and how to make our objects look nicer in ProM.
Time person of the year 2006, Nobel Peace Prize winner 2012.
Hi,
Can you please share the code for importing XES file into Prom?
Thank you.
ProM can do that already; the code is in the ProM source repository at https://svn.win.tue.nl/repos/prom/ I believe loading is done byt the Log pacakge (https://svn.win.tue.nl/repos/prom/Packages/Log/Trunk/), but I’m not entirely certain. In any case, the details are handled by OpenXES, which is available at https://svn.win.tue.nl/repos/prom/OpenXES/trunk/
Hi Mitchell,
Yes, I know, ProM extends importing and exporting functionality. But, I have downloaded and executed the project you suggested in the first part of the tutorial. But, Import plugin is missing in the package what you have suggested to start working with.
As we can write UIimport plugin, I want code of plugin which imports the XES into prom such that I can integrate that with the project.
Thank you.
Run the package manager from your plug-in (there’s a launch configuration for that) and install AllPackages. That does a default installation in your development environment. You want that for lots of reasons anyway.
I tried running package manager from my plug-in the following information I have got in eclipse console.
Usage: java org.processmining.framework.packages.PackageManager [options] COMMAND [command arguments]
-h / –help Print this help message and exit
The following commands are available:
update Retrieve the latest package definitions from all repositories
change Install the packages preceded by +, remove packages preceded by x (example: change +packageA:1.0 xpackageB)
list List all known packages and their status (A=available,I=installed,B=broken,+=has update)
What is the meaning of this?
Thank you