ProM 6 Plug-in Development; Part 4: Connections

In part 1 of this tutorial, we wen over the basics of ProM plug-in development and in parts 2 and 3 we extended our plug-in to better interact with ProM and the user.  In this installment, we’ll look at how to make your plug-in work better with other plug-ins by providing extra information that may be useful about objects.  We do this using the connection mechanism of ProM.

Motivation for Connections

Sometimes we can provide more information about an object in ProM.  This information may not be part of the object, but is a computed result or a relationship to another object.  In our running example, we have a Person object and a way to construct new persons from old persons:
[java] public class Person {
  private Name name;
  private int age

  public Person() {
    age = 0;
  }

  public Name getName() {
    return name;
  }

  setName(Name name) {
    this.name = name;
  }

  public int getAge() {
    return age;
  }

  setAge(int age) {
    this.age = age;
  }
}

@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) {
    Person child = new Person();
    child.setAge(0);
    child.setName(new Name(config.getName(), father.getName().getLast()));
    return child;
  }

// More stuff from the previous parts
}
[/java] We notice that in our plug-in method (ll. 36-44), we only use the last name of the father and don’t use the mother at all. After executing the plug-in, there is no knowledge of the relationship between the parents and the child.

We could of course add this relationship directly to our Person class (ll. 1-24), just like we have information about the name and age of the person. This solution has three drawbacks, however.

First, we may not have access to the Person object. Maybe it was developed by somebody else. In fact, unless I decide to proof-read this tutorial (and I didn’t with any of the other parts, so why should this one be any different), for any reader that will be the case. This may mean we cannot change the original object.

It is also not necessarily a good idea to add all information which may not be relevant to every object. For example, going to the bible and instantiating an object for Adam, the meaning of a father relationship is ambiguous at best and a mother relationship makes not sense at all, so why should the object carry information about a non-existing relationship? Sometimes such a relationship is also unimportant; if we are making persons from genetic engineering and magic, so any parent relationship is ambiguous, and all we are interested in is the age of persons, why should we care about a completely irrelevant relationship? Adding all relationships may also unnecessarily clutter a model. One can easily argue that adding social security numbers (to handle injuries), more familiar relationships (to avoid cousin weddings), and so on, are reasonable, which would lead to a very cluttered Person model.

Finally, a more technical concern is that adding a relationship from a child to both of the parents keep the objects alive in memory and prevent the garbage collector from freeing up memory when the parents are no longer needed. We can get around this with weak references, but this complicates the implementation and forces users to worry about things that should really be abstracted away.

More Technical Example

While our argumentation so far has been based on our Person example, we also have very good reasons for allowing such relationships in a more process mining-based setting.  Consider a Petri net as an object.  A Petri net can have a lot of auxiliary information.  For example, a Petri net is often associated with an initial marking.  For replay purposes or for workflow nets, a Petri net can also have a final marking.  A final marking makes no sense for a general Petri net.  A Petri net can also have graphical information (colors, positions, sizes, etc. of nodes and arcs), but need not have (e.g., if it is discovered using a mining algorithm).  For analysis, we may also know invariants (place invariants can reduce the amount of memory needed to store markings), boundedness or other safety properties that can be used in analysis, or liveness properties.  We can extend the set of useful information of Petri nets almost arbitrarily, and many algorithms will ignore most of that information (e.g., a replay algorithm does not carte about the layout of a Petri net, but does care about initial and final markings).

A Solution: Connections

To solve this, ProM has a concept of connections.  Connections declaratively specify relationships between provided objects. Plug-ins can get access to a connection manager, which manages all connections, using the PluginContext.  To get started using connections, we should implement the Connection interface.  Rather than doing this from scratch, we do this by subclassing the AbstractConnection class. For example, to create a connection indicating parenthood, we would do as follows:
[java] package org.processmining.plugins.helloworld;

import org.processmining.framework.connections.*;
import org.processmining.framework.connections.impl.*;

public class ParentConnection extends AbstractConnection {
public static final String FATHER = “father”;
public static final String MOTHER = “mother”;
public static final String CHILD = “child”;

public ParentConnection(Person child, Person mother) {
super(“Mother of ” + child.getName() + ” is ” + mother.getName());
put(CHILD, child);
put(MOTHER, mother);
}

public ParentConnection(Person child, Person father, Person mother) {
super(“Parents of ” + child.getName() + ” are ” + father.getName() + ” and ” + mother.getName());
put(CHILD, child);
put(FATHER, father);
put(MOTHER, mother);
}
}
[/java] Here, we store information of the father and (optionally) mother of a child. All members of a connection must have an explicit role. The role is a string, and good practice is to create the roles as constant members of the connection to avoid forcing users to use unchecked string constants. We define the 3 possible relationships in lines 7-9. We define two constructors, one for when we define both parents and one where we define the mother only (maybe the father is unknown). Each constructor defines the relevant fields using put.

We can define some annotations of connections, but for simple use that is not necessary. If we want, we can define a ConnectionDoesntExistMessage which takes a single String parameter message, which is returned to users if a particular connection does not exist, or a ConnectionObjectFactory, which is an annotation for Plugins to signify they can produce connections for a particular set of objects. This can be used to automatically generate connections.

Using Connections

To use a connection (assuming we do not use connection factories), we need to register it with the connection manager. For example, this is as simple as:
[java] @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) {
    Person child = new Person();
    child.setAge(0);
    child.setName(new Name(config.getName(), father.getName().getLast()));
context.addConnection(new ParentConnection(child, father, mother));
    return child;
  }

// More stuff from the previous parts
}
[/java] The only new part is line 18, where we create and add our newly created connection.

Now, we will probably want to use our connection. Consider we want to scold a person. If said person is younger than 18, we want this to fall back on the father if he is known, and otherwise on the mother (if known). We can do this using this plug-in:
[java] @Plugin(name = “Scold”,
        parameterLabels = { “Vicim” },
        returnLabels = { “Scolding” },
returnTypes = { Scolding.class })
public class ScoldPlugin {
  @UITopiaVariant(affiliation = “University of Life”,
                  author = “Britney J. Spears”,
                  email = “britney@westergaard.eu”,
                  uiLabel = UITopiaVariant.USEPLUGIN)
  @PluginVariant(requiredParameterLabels = { 0 })
  public static Scolding scold(final PluginContext context,
                            final Person victim) {
if (victim.getAge() < 18) {
try {
for (ParentConnection c : context.getConnectionManager().getConnections(ParentConnection.class, victim)) {
if (c.getObjectWithRole(ParentConnection.CHILD) == victim) {
Person toScold = c.getObjectWithRole(ParentConnection.MOTHER);
if (toScold == null)
toScold = c.getObjectWithRole(ParentConnection.FATHER);
if (toScold != null)
context.getProvidedObjectManager().createProvidedObject(
“Bad Parent”, new Scolding(toScold), Scolding.class, context);
}
}
} catch (ConnectionCannotBeObtained _) {
// Ignore; nobody cares about orphans anyway
}
}
return new Scolding(victim);
  }

// More stuff from the previous parts
}
[/java] We assume the existence of a Scolding object which is the product of this plug-in. The interesting part is in the case when the victim is less than 18 years old, i.e., lines 14-27. Here, we cat all ParentConnections involving the victim (l. 15). There may be multiple (the victim may be a child or a parent themself). If we are sure that there is at most one connection of the given type, we can also use the getConnection (note singular) method, which returns just a random connection (or null of none exist). We check that the victim is indeed the child (to ignore the case when the victim is one of the parents) (l. 16). Then we resolve the responsible parent (ll. 17-19). We first try finding the mother (l. 17). If this object is null (l. 18), we revert to the father (l. 19). Now, if we found a person responsible (l. 20), we create a new Scolding and publish it using the POM as explained in Part 3. This is all surrounded by a try-catch block, handling a ConnectionCannotBeObtained which is raised if there is no ParentConnection for the given victim.

Persistent Connections

Until now, all our persons have been genderless.  While this is all neo-politically correct, it is a fact that most people do actually have a gender.  This can, e.g., be important for a plug-in granting drivers licenses, where we naturally only want to grant one if the person is a male.  Alas, our Person object does not have a gender.

We would like to use a connection to assign a gender to a person, but as connections so far only maintain weak references to objects that are managed by the POM, this is not immediately possible.  We can however add such auxiliary information that is important but does not carry enough information to be published in the POM.

We can do this by using the AbstractStrongReferencingConnection class as base class instead of the AbstractConnection. We can, e.g., create a connection assigning genders to Persons as such:
[java] package org.processmining.plugins.helloworld;

import org.processmining.framework.connections.*;
import org.processmining.framework.connections.impl.*;

public class GenderConnection extends AbstractStrongReferencingConnection {
  public static final String PERSON = “person”;
  public static final String GENDER = “gender”;

public enum Gender { MALE, FEMALE };

  public GenderConnection(Person person, Gender gender) {
    super(“Gender of ” + person() + ” is ” + gender);
    put(PERSON, person);
    putStrong(GENDER, gender);
  }
}
[/java]

Here, we inherit from AbstractStrongReferencingConnection in line 6 and add the gender using the putStrong method in line 15. Using this method instead of the regular put method ensures that the object is not garbage collected and makes the connection ignore it when cleaning up, so when a person is dead and removed by the garbage collector, their gender is removed as well. Other than that, this connection should be self-explanatory and can be used as any other connection.

Conclusion

This concludes the part on connections. Connections are a means to add extra information to existing objects. In the next installment, we shall take a look at automatic testing of plug-ins.

One thought on “ProM 6 Plug-in Development; Part 4: Connections

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.