Process events are stored in the database synchronously and within the same transaction as actual process instance execution. That obviously takes some time especially in highly loaded systems and might have some impact on the database when both history log and runtime data are kept in the same database. To provide an alternative option for storing process events, a JMS based logger has been provided. It can be configured to submit messages to JMS queue instead of directly persisting them in the database. It can be configured to be transactional as well to avoid issues with inconsistent data in case of jBPM engine transaction is rolled back.
ConnectionFactory factory = …;
Queue queue = …;
StatefulKnowledgeSession ksession = …;
Map<String, Object> jmsProps = new HashMap<String, Object>();
jmsProps.put(“jbpm.audit.jms.transacted”, true);
jmsProps.put(“jbpm.audit.jms.connection.factory”, factory);
jmsProps.put(“jbpm.audit.jms.queue”, queue);
AbstractAuditLogger auditLogger = AuditLoggerFactory.newInstance(Type.JMS, ksession, jmsProps);
ksession.addProcessEventListener(auditLogger);
// invoke methods one your session here
This is just one of possible ways to configure JMS audit logger, see javadocs for AuditLoggerFactory for more details.
10.2.4. Variables auditing
Process and task variables are stored in audit tables by default although there are stored in simplest possible way - by creating string representation of the variable - variable.toString(). In many cases this is enough as even for custom classes used as variables users can implement custom toString() method that produces expected “view” of the variable.
Though this might not cover all needs, especially when there is a need for efficient queries by variables (both task and process). Let’s take as an example a Person object that has following structure:
public class Person implements Serializable {
private static final long serialVersionUID = -5172443495317321032L;
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
while at first look this seems to be sufficient as the toString() methods provide human readable format it does not make it easy to be searched by. As searching through strings like "Person [name=“john”, age=“34”] to find people with age 34 would make data base query very inefficient.
To solve the problem variable audit has been based on VariableIndexers that are responsible for extracting relevant parts of the variable that will be stored in audit log.
/**
-
Variable indexer that allows to transform variable instance into other representation (usually string)
-
to be able to use it for queries.
-
@param type of the object that will represent indexed variable
*/
public interface VariableIndexer {/**
- Tests if given variable shall be indexed by this indexer
- NOTE: only one indexer can be used for given variable
- @param variable variable to be indexed
- @return true if variable should be indexed with this indexer
*/
boolean accept(Object variable);
/**
- Performs index/transform operation of the variable. Result of this operation can be
- either single value or list of values to support complex type separation.
- For example when variable is of type Person that has name, address phone indexer could
- build three entries out of it to represent individual fields:
- person = person.name
- address = person.address.street
- phone = person.phone
- that will allow more advanced queries to be used to find relevant entries.
- @param name name of the variable
- @param variable actual variable value
- @return
*/
List index(String name, Object variable);
}
By default (indexer that takes the toString()) will produce single audit entry for single variable, so it’s one to one relationship. But that’s not the only option: indexers (as can be seen in the interface) returns list of objects that are the outcome of single variable indexation.
To make our person queries more efficient we could build custom indexer that would take Person instance and index it into separate audit entries one representing name and the other representing age.
public class PersonTaskVariablesIndexer implements TaskVariableIndexer {
@Override
public boolean accept(Object variable) {
if (variable instanceof Person) {
return true;
}
return false;
}
@Override
public List<TaskVariable> index(String name, Object variable) {
Person person = (Person) variable;
List<TaskVariable> indexed = new ArrayList<TaskVariable>();
TaskVariableImpl personNameVar = new TaskVariableImpl();
personNameVar.setName("person.name");
personNameVar.setValue(person.getName());
indexed.add(personNameVar);
TaskVariableImpl personAgeVar = new TaskVariableImpl();
personAgeVar.setName("person.age");
personAgeVar.setValue(person.getAge()+"");
indexed.add(personAgeVar);
return indexed;
}
}
That indexer will then be used to index Person class only and rest of variables will be indexed with default (toString()) indexer. Now when we want to find process instances or tasks that have person with age 34 we simple refer to it as
variable name: person.age
variable value: 34
There is not even need to use like based queries so data base can optimize the query and make it efficient even with big set of data.
Building and registering custom indexers
Indexers are supported for both process and task variables. though they are supported by different interfaces as they do produce different type of objects representing audit view of the variable. Following are the interfaces to be implemented to build custom indexers:
process variables: org.kie.internal.process.ProcessVariableIndexer
task variables: org.kie.internal.task.api.TaskVariableIndexer
Implementation is rather simple, just two methods to be implemented
accept - indicates what types are handled by given indexer. Note that only one indexer can index given variable, so the first that accepts it will perform the work
index - actually does the work to index variables depending on custom requirements
Once the implementation is done, it should be packaged as jar file and following file needs to be included:
for process variables: META-INF/services/org.kie.internal.process.ProcessVariableIndexer with list of FQCN that represent the process variable indexers (single class name per line in that file)
for task variables: META-INF/services/org.kie.internal.task.api.TaskVariableIndexer with list of FQCN that represent the task variable indexers (single class name per line in that file)
Indexers are discovered by ServiceLoader mechanism and thus the META-INF/services files need. All found indexers will be examined whenever process or task variable is about to be indexed.
Only the default (toString() based) indexer is not discovered but added explicitly as last indexer to allow custom ones to take the precedence over it.