In Part 1 of this tutorial we went over the basics, and in Part 2 we made our plug-in integrate better with ProM by making it possible to load and save objects, by providing a user-interface for configuration, and by providing progress and logging information when executing a plug-in. In this part, we look at more advanced ways to export/import objects to/from the workspace, more elaborate configuration using wizards, and how to make your objects feel more at home in ProM. I’ll assume you are familiar with the content of the previous parts, in particular the example from Part 1.
Provided Objects Manager
All objects visible in the ProM user-interface are called provided objects. They are managed by the provided object manager (POM). All plug-ins have access to this using their PluginContext and the method context.getProvidedObjectManager.
The POM is responsible to keeping track of all objects that are still live. We can create new provided objects and get information about existing provided objects. All objects produced by plug-ins are automatically registered with the POM, but sometimes a plug-in may wish to register further objects or to automatically search for existing objects. If we are searching for specific objects related to one of the parameters, ProM has a much nicer mechanism which will be the entire topic for the next installment, so keep that in mind before you start searching for the objects in the POM.
In our example, we produce and populate a configuration in the graphical version of our plug-in. It would make perfect sense to export this to the workspace so a user can reuse it. We could of course return the configuration as another result, but it is not so important we want to clutter the interface with that. Furthermore, all variants of a plug-in must have the same return values, and it makes no sense to export the configuration for the non GUI variant, which gets the configuration as a parameter. Instead, we export it directly in the code using the provided object manager.
It also makes sense to have the plug-in search for a pre-existing configuration in the graphical variant. If one exists it is used populate the new configuration instead of a blank one. This is a convenience if we want to execute a plug-in again with the same settings.
Here is code implementing both of these:
[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.providedobjects.*;
@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) {
ProcreationConfiguration config = new ProcreationConfiguration();
for (ProvidedObjectID id : context.getProvidedObjectManager().getProvidedObjects) {
try {
Class<?> clazz = context.getProvidedObjectManager().getProvidedObjectType(id);
if (ProcreationConfiguration.class.isAssignableFrom(clazz)) {
Object o = context.getProvidedObjectManager().getProvidedObjectObject(id, false));
if (o instanceof ProcreationConfiguration) {
ProcreationConfiguration c = (ProcreationConfiguration) o;
config.setName(c.getName());
break;
}
}
} catch (ProvidedObjectDeletedException _) {
// Ignore
}
}
config = populate(context, config);
context.getProvidedObjectManager().createProvidedObject(“Procreation Configuration”,
config,
ProcreationConfiguration.class,
context);
return procreate(context, father, mother, config);
}
// Variant with configuration and populate method go here
}
[/java]
The simplest part is registering the new configuration, lines 39-42. All we do is that we create a new provided object with the given name, value, type, and PluginContext. The type is in principle optional, but it is always a good idea to provide it as ProM can get confused if you use an anonymous subclass or a named subclass that has not already been used as a parameter or return type of a plug-in. By specifying the explicit type, we circumvent problems. We should check that the specified configuration has indeed been changed if we loaded an existing one, and that the user did not cancel.
Finding an existing configuration is more difficult, as the POM is not really made for this. All we can get is a list of all identifiers of all provided objects. The intuition is that a provided object may not be on the local machine. We therefore iterate over all such ids (ll. 23-37). We wrap any handling in a try-catch statement as a provided object may be deleted while processing, causing the code to throw an exception. For each object, we check that the type of the object is a ProcreationConfiguration (ll. 25-26). We do it like this rather then just testing directly on the type (ll. 27-28) as the call to get the actual object may be expensive if it is not available locally. If the object is the correct type, we fetch it and test the type. When fetching the object (l. 27), we provide false as a second parameter. This means that if the object is not yet fully computed, we get a Future instead of the actual object. In line 28 we check that the object is actually of the correct type and not a Future (or null). If the object has the correct type, we update the existing configuration (l. 30). We need to do this as our populate method alters the object it is given and we should never change an object managed by the POM.
We notice that objects created using context.getProvidedObjectManager().createProvidedObject does not show up normally in the ProM workspace. This is because it is not marked as a favorite object, and we have to switch to the all tab. For a configuration, this is completely sensible, but if we export other objects we may want to show them as favorite objects. This is not possible in ProM, but the Widgets packet has a utility class which makes it possible to further manipulate provided objects. For example, we can call
[java]
org.processmining.plugins.utils.ProvidedObjectHelper.setFavorite(context, config);
[/java]
after line 40 to mark the object as favorite.
The class also has a method, publish, which is similar to the createProvidedObject of the POM except it works while a plug-in is running. That way it is possible to publish and show intermediate results while a plug-in is running. This is, e.g., useful for a genetic miner, where intermediate results can be shown and manipulated while the plug-in is running.
Wizards
To perform more advanced configuration, using a wizard is often a good idea. The Widgets package contains a general mechanism for producing wizards. In general a wizard comprises a partial order of steps, each step providing a configuration or information screen. Users can navigate back and forward between the different steps, and which forward step is used depends on the configuration options.
Often, this generality is not needed in practice, and a wizard is solely used to present a list of configuration options. Here, we just present this simple use case, but refer to the MapWizard class or the completely generic ProMWizard interface, and Jan-Martijn’s more in-detail description of the design and implementation of the Widget framework. For more examples of more advanced use of the framework, you can refer to the Ontologies package.
As mentioned, the basic building block of wizards are steps. Steps, not the band but the ones for wizards, must implement the interface ProMWizardStep, depicted below:
[java]
public interface ProMWizardStep<SettingsModel> {
String getTitle();
JComponent getComponent(SettingsModel model);
boolean canApply(SettingsModel model, JComponent component);
SettingsModel apply(SettingsModel model, JComponent component);
}
[/java]
The interface is parametrized with a SettingsModel, which is the configuration object for our plug-in. In our example, that would be ProcreationConfiguration. The getTitle returns a title for the particular step, and getComponent returns a component to be displayed at this step. The component should be pre-filled with the configuration given as parameter.
The canApply and apply methods are invoked when a user presses the next or finish buttons. The first returns whether the settings are consistent and the widget can progress to the next step and the second actually applies the settings from the widget. If the filled in values are not consistent or incomplete, canApply returns false, and the user cannot progress. Otherwise, apply is called and the step is expected to update the SettingsModel according to the component.
An example step for configuring the ProcreationConfiguration could look like this:
[java]
package org.processmining.plugins.helloworld;
import javax.swing.*;
import org.processmining.framework.util.ui.widgets.*;
public class NameStep<M> implements ProMWizardStep<ProcreationConfiguration> {
public String getTitle() {
return “Name the Mistake”;
}
private class ProcreationPanel extends ProMPropertiesPanel {
private final ProMTextField name;
public ProcreationPanel(ProcreationConfiguration config) {
super(“Configure Procreation”);
name = panel.addTextField(“Name”, config.getName());
}
public String getName() {
return name.getValue();
}
}
public JComponent getComponent(final ProcreationConfiguration config) {
return new ProcreationPanel(config);
}
public boolean canApply(final ProcreationConfiguration model,
final JComponent component) {
ProcreationPanel panel = (ProcreationPanel) component;
return !””.equals(panel.getName());
}
public ProcreationConfiguration apply(final ProcreationConfiguration model,
final JComponent component) {
ProcreationPanel panel = (ProcreationPanel) component;
model.setName(panel.getName());
return model;
}
}
[/java]
The major thing to notice is that we make an explicit class for the component. This should always be done as we have no guarantee that a step may be used for multiple configurations, so saving state information inside the step is bad form. By making a named class, we can cast to it and use accessor methods to access the data.
The getTitle and getComponent methods should be self-explanatory. The canApply method checks whether the user has actually entered a name, as unnamed children is just plain silly.
Here, the apply method returns the configuration object it is given. For a linear configuration this is fine as the values changed by any two steps are disjoint. For a wizard with more complex behavior it is better to return a new SettingsModel (as the functional method interface suggests). This makes it possible for the code to maintain alternative partial configurations for different branches. If you do not understand this, just stick to the list wizard explained here and make your steps as in the above example.
The Widgets package ships with simple steps that are commonly used. Most actually configuration steps will have the form above, using the properties panel, but we may also want to provide instructions to the user. For this, we can use the pre-defined TextStep. See the example below for an example use.
Finally, we need to create our wizard and show it. This is the easy part as all the wizard logic is handled by the ProMWizardDisplay class. Assuming the existence of our NameStep above, the populate method of out ProcreatePlugin would look like this:
[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 go here
public static ProcreationConfiguration populate(final UIPluginContext context,
final ProcreationConfiguration config) {
ProMWizard<ProcreationConfiguration> wizard =
new ListWizard<ProcreationConfiguration>(
TextStep.create(“Name that Bastard”, “<html><p>In the following screen, name the offspring<p>No silly names, please!”),
new NameStep())
return ProMWizardDisplay.show(context, wizard, config);
}
}
[/java]
We see that using the wizard is actually simpler than performing configuration ourselves as soon as everything is set up. We first set up our wizard (ll. 18-21). The ListWizard takes a list of wizard steps as parameter and configures itself accordingly. The first step is the TextStep (l. 20), which takes a title and main content, both as strings. It can display HTML in the main content. The second step is our previously created NameStep.
To show the wizard, we just call the static show method of the ProMWizardDisplay class. It takes a context, a wizard and an initial configuration. The wizard is run, including back and forward steps, and when the user completes the last page or presses cancel, the method returns. If the user cancelled null is returned, otherwise an appropriate configuration is returned.
We can of course add an arbitrary number of steps to the wizard and can also show a concluding page with the entire configuration.
Authored Objects
Until now, we have just created our objects naïvely as Java objects. If we want them to really feel at home in ProM, we can add annotations to them describing them, so ProM can show more information than just the class name. This should only be done for objects that are only meant to be used in ProM, as they cannot be used outside ProM with these annotations. Here is an annotated version of the Person class from Part 1:
[java]
package org.processmining.plugins.helloworld;
import org.processmining.framework.annotations.*;
@AuthoredType(typeName = “Person object”,
affiliation = “University of Life”,
author = “Britney J. Spears”,
email = “britney@westergaard.eu”)
@Icon(icon = “resourcetype_person_30x35.png”)
public class Person {
// The same stuff we used to have here
}
[/java]
The AuthoredType annotation (ll. 5-8) specifies the same information as for Plugins: the name, e-mail, and affiliation of the author, and a name for the type. The Icon annotation specifies the location of an image that should be used to represent objects of this type.
Conclusion
Thus, we have seen how to interact with objects in the ProM workspace, how to make more advanced configuration, and how to make your objects stand out in the ProM GUI. In the next installment, we turn to a mechanism for associating auxiliary information to objects, the connection mechanism of ProM.
Time person of the year 2006, Nobel Peace Prize winner 2012.
There seems to be an error in lines 18-21 of your wizard example, as the ProMWizard object needs more arguments. Nevertheless, great tutorial!
Thanks. None of the tutorial code has been tested as I’m super lazy. Some of it may also be based on earlier versions – especially the Widgets package is a fast-moving target.
Frist of all: Thank you so much for this tutorial series. It’s really helpful.
There are some minor coding issues, when I tried to run the code by myself. Most of them were easy fixes. But in this part I stumbled about a Problem with the Wizard. Maybe you can help.
In the Line “ProMWizard wizard =
new ListWizard(
TextStep.create(“Name that Bastard”, “In the following screen, name the offspringNo silly names, please!”),
new NameStep());”
I get the error message:
“Incorrect number of arguments for type ProMWizard; it cannot be parameterized with arguments
I guess ProcreationConfig would be the SettingsModel, but what should I add for WizardModel?
Hi Manu,
You’re very welcome – I’m, glad, you find it useful.
Unfortunately, the tutorial is quite old by now, more than 5 years. While most of the concepts should still apply, some of the technicalities may not anymore as ProM has evolved.
The easiest is probably to look at the source code for the ProMWizard and see which parameters it required now; most likely a new parameter has been added or an old removed.
Hope this at least points you in the right direction,
Michael