Magnolia 5.7 reached extended end of life on May 31, 2022. Support for this branch is limited, see End-of-life policy. Please note that to cover the extra maintenance effort, this EEoL period is a paid extension in the life of the branch. Customers who opt for the extended maintenance will need a new license key to run future versions of Magnolia 5.7. If you have any questions or to subscribe to the extended maintenance, please get in touch with your local contact at Magnolia.
jBPM persistence
jBPM implements a persistence layer based on JPA and Hibernate.
jBPM allows the persistent storage of certain information. This chapter describes these different types of persistence, and how to configure them. An example of the information stored is the process runtime state. Storing the process runtime state is necessary in order to be able to continue execution of a process instance at any point, if something goes wrong. -- jBPM: Persistence and Transactions
In short the persistence allows shutting down the system and restoring the state of all running processes upon restart.
In order to not have to set up a separate storage mechanism in Magnolia for the jBPM engine, Magnolia provides its own JCR persistence layer for jBPM.
JCR persistence
Magnolia stores all runtime data from the the jBPM engine in the workflow
workspace. By default workflow is only enabled on the author instance.
jBPM stores most of the data used for its execution as binary data using marshalling mechanisms. This makes the underlaying scheme rather simple.
Session
As we are using the singleton strategy for our runtime engine we are only dealing with one session. This session is stored under the sessions
node with 0
as static identifier and is never removed. All data is marshalled into the binary property bytes
.
The session ID is not using the key generator Magnolia uses for storing processes and workItems. One reason is that by using the singleton strategy this is not necessary at the moment at least. Another reason ist that the interfaces and implementation classes used inside the jBPM persistence package is restricted to use integers as IDs, compared to processInstances and workItems using a long.
Marshalling
The marshalling is done inside the SessionInfo's update method, which is an object used and created by the CommandService.
protected void initNewKnowledgeSession(KieBase kbase, KieSessionConfiguration conf) { this.sessionInfo = new SessionInfo(); ... this.marshallingHelper = new SessionMarshallingHelper( this.ksession, conf ); this.sessionInfo.setJPASessionMashallingHelper( this.marshallingHelper ); ... this.commandService = new TransactionInterceptor(kContext); }
public void update() { this.rulesByteArray = this.helper.getSnapshot(); }
SessionStore
Persisting the SessionInfo is handled by the JcrSessionStore
implementation in SystemContextSessionStore
where all operations are performed in Magnolia's SystemContext
.
ProcessInstance
Processes are stored under the processInstances
node inside the workflow
workspace. As this data is used during the actual execution of processes, the data is removed when the process terminates. Magnolia's current implementation does not support logging completed processes for further auditing. For more information how this could be implemented, see the documentation of jBPM Audit data model.
Key generator
For creating the IDs for process instances we use the ProcessInstanceIdGenerator
which creates a Long from the current system time. The processes are further stored hierarchical based on year, month of year and day of month.
Marshalling
Similar to the sessions, the processes use a marshalling mechanism and most of the runtime data is stored as a binary under the bytes property. The marshalling is performed inside ProcessInstanceInfo's update method. The info object is created by JcrProcessInstanceManager
.
ProcessInstanceStore
Persisting the ProcessInstanceInfo is handled by the JcrProcessStore
implementation in SystemContextProcessStore
where all operations are performed in Magnolia's SystemContext
. Because we do not need the correlation key
used for mapping sessions to processInstances due to the singleton strategy
, those methods are currently not implemented.
Workitem
Key generator
Workitems are using the same hierarchical structure and keys as the processInstances. The key generator used is implemented in WorkItemIdGenerator
.
Marshalling
The marshalling of work items happens in the update method of the WorkItemInfo
object and the binary data is stored under the bytes
property.
WorkItemStore
Persisting the WorkItemInfo is handled by the JcrWorkItemStore
implementation in SystemContextWorkitemStore
where all operations are performed in Magnolia's SystemContext
.
Safe points
Contrary to the simple storage scheme finding the right spots to persist the current state of a process is a bit more tricky. These spots are called safe points:
The state of a process instance is stored at so-called "safe points" during the execution of the process engine. Whenever a process instance is executing (for example when it started or continuing from a previous wait state, the engine executes the process instance until no more actions can be performed (meaning that the process instance either has completed (or was aborted), or that it has reached a wait state in all of its parallel paths). At that point, the engine has reached the next safe state, and the state of the process instance (and all other process instances that might have been affected) is stored persistently. – jBPM: Safe Points
These safe points are reached by different classes. Some of of the logic is taken care of by the ProcessInstanceManager
and WorkItemManager
where the state is persisted when the execution starts or is completed. As this is not sufficient for keeping the persisted state updated at all times we hook into the internal execution of a process with the CommandService
.
CommandService
To persist the processes at safe points, Magnolia uses a CommandService which allows intercepting internally used Commands.
When loading or creating a KieSession
by the JcrSessionFactory
it delegates the creation to JcrKieStoreServices
which in turn creates a CommandBasedStatefulKnowledgeSession
, an implementation of the KieSession
creating Commands for each step of the process.
As an example this is how the CommandBasedStatefulKnowledgeSession
starts a process by creating a StartProcessCommand
containing the processId and the parameters. You also see how the actual execution of the command is delegated to the commandService
.
... public ProcessInstance startProcess(String processId, Map<String, Object> parameters) { StartProcessCommand command = new StartProcessCommand(); command.setProcessId( processId ); command.setParameters( parameters ); return commandService.execute( command ); } ...
When not using the CommandBasedStatefulKnowledgeSession
this would create a ProcessInstance
and directly start it.
Interceptors
The interceptors used for persisting the state are registered by the JcrSessionFactory
to the commandService
.
The concept behind these interceptors is rather simple. They act as CommandExecutors for commands and allow adding custom logic before and after executing the command. As an example let's take a look at the JcrPersistProcessInterceptor
which takes care of persisting ProcessInstances.
public JcrPersistProcessInterceptor(SimpleSessionCommandService interceptedService) { this.interceptedService = interceptedService; } @Override public <T> T execute(Command<T> command) { T result = null; try { result = executeNext(command); } ... if (isValidCommand(command)) { executeNext(new PersistProcessCommand(jpm, ksession)); } ... } protected boolean isValidCommand(Command<?> command) { return (command instanceof StartProcessCommand) || (command instanceof CreateProcessInstanceCommand) || ... (command instanceof FireAllRulesCommand); }
Note how the interceptor creates and executes a PersistProcessCommand
in case the Command met the criteria of isValidCommand(command)
. Persisting the ProcessInstance is then taken care of by the PersistProcessCommand.
public PersistProcessCommand(Object jpm, KieSession ksession) { this.persistenceContext = ((ProcessPersistenceContextManager) jpm).getProcessPersistenceContext(); this.ksession = ksession; } @Override public Void execute(Context context) { ... ProcessInstanceInfo info = new ProcessInstanceInfo(instance, ksession.getEnvironment()); info.setId(instance.getId()); info.update(); persistenceContext.persist(info); ... }
A similar interceptor is used for persisting the session state.
JCR persistence diagram
This diagram shows the most important classes used for persisting sessions, workitems and processes to JCR.