Fork me on GitHub

The CommandContext service is a request-scoped service that reifies the invocation of an action on a domain object into an object itself. This reified information is encapsulated within the Command object.

By default, the Command is held in-memory only; once the action invocation has completed, the Command object is gone. The optional supporting CommandService enables the implementation of Command to be pluggable. With an appropriate implementation (eg as provided by the isis-command-module-jdo's CommandServiceJdo) the Command may then be persisted.

Persistent Commands support several use cases:

Assuming that the CommandService supports persistent Commands, the associated @Command annotation also allows action invocations to be performed in the background. In this case the act of invoking the action on an object instead returns the Command to the user.

Screencast

The screencast provides a run-through of the command (profiling) service, auditing service, publishing service. It also shows how commands can be run in the background either explicitly by scheduling through the background service or implicitly by way of a framework annotation.

API

The CommandContext request-scoped service defines the following very simple API:

@RequestScoped
public class CommandContext {

    @Programmatic
    public Command getCommand() { ... }
}

where Command is defined in turn as:

public interface Command extends HasTransactionId {

    public abstract String getUser();
    public abstract Timestamp getTimestamp();

    public abstract Bookmark getTarget();
    public abstract String getMemberIdentifier();
    public abstract String getTargetClass();
    public abstract String getTargetAction();
    public String getArguments();
    public String getMemento();

    public ExecuteIn getExecuteIn();
    public Executor getExecutor();
    public Persistence getPersistence();
    public boolean isPersistHint();

    public abstract Timestamp getStartedAt();
    public abstract Timestamp getCompletedAt();
    public Command getParent();

    public Bookmark getResult();
    public String getException();

}    

and where Command has the members:

Implementation

CommandContext

The CommandContext implementation is part of the core framework (isis-core), in fact is a concrete class in the applib:

CommandService (for persistent Commands)

The CommandService interface - which acts as the factory for different Command implementations - is pluggable. The isis-module-command-jdo implementation persists Commands to an RDBMS.

Usage

Explicitly treating an action as a command

The typical way to indicate that an action should be treated as a command is to annotate it with the @Command annotation, for example:

public class ToDoItem ... {

    @Command
    public ToDoItem completed() { ... }

}

The annotation can also be used to specify whether the command should be performed in the background, for example:

public class ToDoItem ... {

    @Command(executeIn=ExecuteIn.BACKGROUND)
    public ToDoItem scheduleImplicitly() {
        completeSlowly(3000);
        return this;
    }
}        

When a background command is invoked, the user is returned the command object itself (to provide a handle to the command being invoked).

This requires that an implementation of CommandService that persists the commands (such as isis-module-command-jdo's CommandService implementation) is configured. It also requires that a scheduler is configured to execute the background commands, see BackgroundCommandService).

Making commands the default

As an alternative to annotating every action with @Command, alternatively this can be configured as the default.

To treat every action as a command, add the following to isis.properties:

isis.services.command.actions=all 

For some applications it may be required to record (as commands) only those actions that update objects; in other words queries and searches can be ignored. Put another way, only actions annotated (with @ActionSemantics) as IDEMPOTENT or NON_IDEMPOTENT should be treated as commands. In this case, use:

isis.services.command.actions=ignoreQueryOnly

To prevent an action from being treated as a command (even if globally enabled), use the @Command annotation with the disabled attribute:

@Command(disabled=true)
public void notACommand() { ... }

To disable globally, use:

isis.services.command.actions=none

If the key is not present in isis.properties, then commands are disabled by default.

Interacting with the services

Typically the domain objects have little need to interact with the CommandContext and Command directly; what is more useful is that these are persisted in support of the various use cases identified above.

One case however where a domain object might want to obtain the Command is to determine whether it has been invoked in the foreground, or in the background. It can do this using the getExecutedIn() method:

ExecutedIn executedIn = commandContext.getCommand().getExecutedIn();

If run in the background, it might then notify the user (eg by email) if all work is done.

This leads us onto a related point, distinguishng the effective user vs the real user. When running in the foreground, the current user can be obtained from the DomainObjectContainer, using:

String user = container.getUser().getName();

If running in the background, however, then the current user will be the credentials of the background process, for example as run by a Quartz scheduler job.

The domain object can still obtain the original ("effective") user that caused the job to be created, using:

String user = commandContext.getCommand().getUser();

Registering the Services

Assuming that the configuration-and-annotation services installer is configured:

isis.services-installer=configuration-and-annotation

then the CommandContext service is automatically registered and injected into your entities/services; no further configuration is required.

Related Services

As discussed above, the supporting CommandService enables Command objects to be persisted. Other related services are the BackgroundService and BackgroundCommandService). For BackgroundService captures commands for execution in the background, while the [BackgroundCommandService] persists such commands for execution.

The implementations of CommandService and BackgroundCommandService are intended to go together, so that persistent parent Commands can be associated with their child background Commands.