Command Log
The Command Log module provides an implementation of CommandSubscriber SPI that persists Commands using either the JPA/EclipseLink or JDO/DataNucleus object store.
One use case is to combine with the Audit Trail extension. The Command Log module logs the action invocations or property edits that the end-user makes, while the audit trail logs the resultant changes in state to domain objects. The two logs are correlated using the interactionId of the owning interaction.
Another use case is to support (persisted) background commands, whereby actions are not invoked immediately but instead persisted and invoked by a background thread; this is described in the background commands section below.
Sometimes the Execution Log extension is also configured with or instead of this extension; see the notes below to compare and contrast.
Setup
Dependency Management
Add an entry for the Command Log module’s own BOM:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.causeway.extensions</groupId>
<artifactId>causeway-extensions-commandlog</artifactId>
<scope>import</scope>
<type>pom</type>
<version>2.1.0</version>
</dependency>
</dependencies>
</dependencyManagement>
Dependencies / Imports
In the webapp module of your application, add the following dependency:
<dependencies>
<dependency>
<groupId>org.apache.causeway.extensions</groupId>
<artifactId>causeway-extensions-commandlog-persistence-XXX</artifactId> (1)
</dependency>
</dependencies>
1 | either:
|
In your application’s App Manifest, import the CommandLog modules. The exact modules to use will depend upon the persistence mechanism in use:
@Configuration
@Import({
...
CausewayModuleExtCommandLogPersistenceXxx.class, (1)
...
})
public class AppManifest {
}
1 | either
|
Configuration Properties
Add the database schema used by the Command Log entities to the configuration file:
causeway:
persistence:
schema:
auto-create-schemas: causewayExtCommandLog
Optionally, modify the configuration properties for the Command Log module itself:
causeway:
extensions:
command-log:
publish-policy: "always" (1)
1 | the alternative is "only-if-system-changed" , which suppresses the persisting of `CommandLogEntry`s for commands where no other system state was changed (for example a finder action with safe semantics).
See causeway.extensions.command-log.publish-policy configuration property for more details. |
menubar.layout.xml
Once configured, the extension provides a number of menu actions.
You can use menubars.layout.xml
to arrange these as you see fit.
To get you started, the following fragment adds all of the actions to an "Activity" secondary menu:
<mb:secondary>
...
<mb:menu>
<mb:named>Activity</mb:named>
...
<mb:section>
<mb:named>Commands</mb:named>
<mb:serviceAction id="activeCommands" objectType="causeway.ext.commandLog.CommandLogMenu"/>
<mb:serviceAction id="findMostRecent" objectType="causeway.ext.commandLog.CommandLogMenu"/>
<mb:serviceAction id="findCommands" objectType="causeway.ext.commandLog.CommandLogMenu"/>
<mb:serviceAction id="findAll" objectType="causeway.ext.commandLog.CommandLogMenu"/>
</mb:section>
...
</mb:menu>
</mb:secondary>
SecMan Security Roles
If SecMan extension is configured, then permissions must be granted to access the menu actions.
This can be done by granting the role set up by the CausewayExtCommandLogRoleAndPermissions seed fixture script (see its ROLE_NAME
constant).
User Interface
The extension provides a number of menu actions and contributions.
The menu actions are as listed in menubar.layout.xml, above. They allow the administrator to query the persisted commands. Typically access to these actions would be restricted, see security roles above.
The extension also provides these mixins:
-
This contributes a
recentCommands
collection to each and every domain object.This can be explicit positioned through the domain class' own layout file, but this is generally not necessary: it will slot into the tab group in the layout file indicated for unreferenced collections using
<tabGroup unreferencedCollections="true">
. -
HasUsername_recentCommandsByUser
This contributes the
recentCommandsByUser
collection to any domain object that implements the HasUsername interface.Most notably, this is SecMan extension’s ApplicationUser entity that represents a logged-on user. It is also supported by xref:security:sessionlog:about.adoc
-
HasInteractionId_commandLogEntry
This contributes the
commandLogEntry
property to any object implementing HasInteractionId interface. Typically these are the entities persisted by the Execution Log or Audit Trail extensions, making it easy to traverse between these logs.
Background Commands
Sometimes we might want to execute an action not immediately in the current users’s thread of control, but instead to perform it in the background; for example any long-running process.
One way to accomplish this is to use WrapperFactory#asyncWrap(…), where the command is executed by another thread obtained from a thread pool (ForkJoinPool.commonPool()
).
This works well, but has the slight risk that it is not transactionally safe - the async thread executes in its own interaction/transaction, and so might fail even though the initiating command succeeds; or vice versa.
An alternative approach is to use the BackgroundService.
This persists the command as an CommandLogEntry
instance, indicating that it is to be executed in the background.
Then, a separate thread - eg scheduling using Quartz - can pick up the queued CommandLogEntry
and execute it.
Submitting Actions
For example, suppose we have a long-running action to export all the invoices we have received from a supplier, perhaps to be sent to some other system.
Assuming that the exportInvoices()
action is a regular action on the Supplier
domain class, we would use:
BackgroundService
to invoke a regular action@Action
public void exportInvoices(Supplier supplier) {
backgroundService.execute(supplier).exportInvoices();
}
If instead this functionality is implemented as a mixin, we would use something like:
BackgroundService
to invoke a mixin action:@Action
public void exportInvoices(Supplier supplier) {
backgroundService.executeMixin(Supplier_exportInvoices.class, supplier).act();
}
The action being invoked must be part of the Causeway metamodel, in other words it cannot be marked uses @Programmatic or @Domain.Exclude.
By default all the usual hide/disable/validate rules will be checked, but there are also methods to allow these rules to be skipped.
Behind the scenes this service uses WrapperFactory#asyncWrap(…) using AsyncControl#with(ExecutorService) to pass an implementation of ExecutorService
that persists the command as a CommandLogEntry
instance.
If you require more fine-grained control, you can always just use the WrapperFactory async method yourself.
The ExecutorService
to use is BackgroundService.PersistCommandExecutorService.
This is a Spring @Service
and so can be obtained through injection.
Executing Actions using the Quartz scheduler
Once a command has been persisted as a CommandLogEntry
, we require some other process to actually execute the command.
The Command Log module includes the RunBackgroundCommandsJob
, a Quartz job that does exactly this.
Each time it is called it will query for any background commands that have not been started, and will execute each (using the CommandExecutorService).
The job is marked as non re-entrant, so it doesn’t matter how often it is called; we recommend a 10 second delay usually works fine.
To configure Quartz, add the following to your AppManifest
:
public class AppManifest {
@Bean(name = "RunBackgroundCommandsJob") (1)
public JobDetailFactoryBean jobDetail() {
val jobDetailFactory = new JobDetailFactoryBean();
jobDetailFactory.setJobClass(RunBackgroundCommandsJob.class);
jobDetailFactory.setDurability(true);
return jobDetailFactory;
}
@Bean
public SimpleTriggerFactoryBean trigger(
final @Qualifier("RunBackgroundCommandsJob") JobDetail job) { (1)
val trigger = new SimpleTriggerFactoryBean();
trigger.setJobDetail(job);
trigger.setStartDelay(60_000); (2)
trigger.setRepeatInterval(10_000); (3)
trigger.setRepeatCount(REPEAT_INDEFINITELY);
return trigger;
}
// ...
}
1 | name and qualify the job (so will not interfere with any other Quartz jobs you may have defined) |
2 | 60 secs to wait for the app to be ready |
3 | check every 10 seconds |
Disabling Quartz
The Command Log module automatically references the Quartz library. If you don’t want to use this functionality and want to exclude quartz, then add an explicit dependency on the Command Log applib but exclude the quartz dependency within it:
<dependencies>
<dependency>
<groupId>org.apache.causeway.extensions</groupId>
<artifactId>causeway-extensions-commandlog-applib</artifactId>
<exclusions>
<exclusion>
<groupId>org.quartz-scheduler</groupId> (1)
<artifactId>quartz</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
1 | exclude reference to quartz |
Notes
Conceptually a command represents the intention to execute an action or to edit a property ("before" the change), while an interaction execution represents the actual execution itself ("after" the change).
The CommandSubscriber SPI and ExecutionSubscriber SPI allow either to be subscribed to. From an auditing perspective, their behaviour is quite similar:
-
even though a command represents the intention to invoke an action, its CommandSubscriber SPI is only called once the action/property edit has been completed.
-
the ExecutionSubscriber is called as soon as the action has completed. In most interactions there will only be a single action called within the interaction, hence these two subscribers will be called at almost the same time with very similar payloads.
However, there can be some subtle differences:
-
the WrapperFactory service allows actions to be invoked "as if" through the user interface. Therefore one action can execute another can execute another, creating a nested call graph of executions.
The ExecutionSubscriber is called after each and every execution as it completes, so will be called several times.
-
In contrast, the CommandSubscriber is called only once, for the top-level (outermost) action.
See also
-
WrapperFactory service
-
BackgroundService service
-
Execution Log extension