@Action

Groups together all domain-specific metadata for an invokable action on a domain object or domain service.

API

Action.java
@interface Action {
  String associateWith() default "";     (1)
  String associateWithSequence() default "1";     (2)
  Class<? extends CommandDtoProcessor> commandDtoProcessor() default CommandDtoProcessor.class;     (3)
  Publishing commandPublishing() default Publishing.NOT_SPECIFIED;     (4)
  Class<? extends ActionDomainEvent<?>> domainEvent() default ActionDomainEvent.Default.class;     (5)
  Publishing executionPublishing() default Publishing.NOT_SPECIFIED;     (6)
  Where hidden() default Where.NOT_SPECIFIED;     (7)
  RestrictTo restrictTo() default RestrictTo.NOT_SPECIFIED;     (8)
  SemanticsOf semantics() default SemanticsOf.NOT_SPECIFIED;     (9)
  Class<?> typeOf() default Object.class;     (10)
  String fileAccept() default "";     (11)
}
1 associateWith

Associates this action with a property or collection, specifying its id.

2 associateWithSequence

Specifies the sequence/order in the UI for an action that’s been associated with a property or collection.

3 commandDtoProcessor

The CommandDtoProcessor to process this command’s DTO.

4 commandPublishing

Whether action invocations, captured as Command s, should be published to CommandSubscriber s.

5 domainEvent

Indicates that an invocation of the action should be posted to the org.apache.isis.applib.services.eventbus.EventBusService using a custom (subclass of) ActionDomainEvent .

6 executionPublishing

Whether Execution s (triggered by action invocations), should be published to ExecutionSubscriber s.

7 hidden

Indicates where (in the UI) the action is not visible to the user.

8 restrictTo

Whether the action is restricted to prototyping, or whether it is available also in production mode.

9 semantics

The action semantics, either SemanticsOf#SAFE_AND_REQUEST_CACHEABLE cached , SemanticsOf#SAFE safe (query-only), SemanticsOf#IDEMPOTENT idempotent or SemanticsOf#NON_IDEMPOTENT non-idempotent .

10 typeOf

If the action returns a collection, then this hints as to the run-time type of the objects within that collection.

11 fileAccept

For downloading Blob or Clob , optionally restrict the files accepted (eg .xslx ).

Members

associateWith

Associates this action with a property or collection, specifying its id.

This is an alternative to using MemberOrder#name() . To specify the order (equivalent to MemberOrder#sequence() }), use #associateWithSequence() .

For example @Action(associateWith="items", associateWithSequence="2.1")

If an action is associated with a collection, then any matching parameters will have their choices automatically inferred from the collection (if not otherwise specified) and any collection parameter defaults can be specified using checkboxes (in the Wicket UI, at least).

associateWithSequence

Specifies the sequence/order in the UI for an action that’s been associated with a property or collection.

This is an alternative to using MemberOrder#sequence() , but is ignored if Action#associateWith() isn’t also specified.

For example: @Action(associateWith="items", associateWithSequence="2.1")

commandDtoProcessor

The CommandDtoProcessor to process this command’s DTO.

The processor itself is used by ContentMappingServiceForCommandDto and ContentMappingServiceForCommandsDto to dynamically transform the DTOs.

commandPublishing

Whether action invocations, captured as Command s, should be published to CommandSubscriber s.

domainEvent

Indicates that an invocation of the action should be posted to the org.apache.isis.applib.services.eventbus.EventBusService using a custom (subclass of) ActionDomainEvent .

Subscribers of this event can interact with the business rule checking (hide, disable, validate) and its modification (before and after).

For example:

public class SomeObject{
    public static class ChangeStartDateDomainEvent extends ActionDomainEvent<SomeObject> { ... }

    @Action(domainEvent=ChangedStartDateDomainEvent.class)
    public void changeStartDate(final Date startDate) { ...}
    ...
}

This subclass must provide a no-arg constructor; the fields are set reflectively.

executionPublishing

Whether Execution s (triggered by action invocations), should be published to ExecutionSubscriber s.

hidden

Indicates where (in the UI) the action is not visible to the user.

It is also possible to suppress an action’s visibility using ActionLayout#hidden() .

For DomainService actions, the action’s visibility is dependent upon its DomainService#nature() nature .

restrictTo

Whether the action is restricted to prototyping, or whether it is available also in production mode.

By default there are no restrictions, with the action being available in all environments.

semantics

The action semantics, either SemanticsOf#SAFE_AND_REQUEST_CACHEABLE cached , SemanticsOf#SAFE safe (query-only), SemanticsOf#IDEMPOTENT idempotent or SemanticsOf#NON_IDEMPOTENT non-idempotent .

The action’s semantics determine whether objects are modified as the result of invoking this action (if not, the results can be cached for the remainder of the request). If the objects do cause a change in state, they additionally determine whether re-invoking the action would result in a further change.

There are also …​ARE_YOU_SURE variants (@link SemanticsOf#IDEMPOTENT_ARE_YOU_SURE and (@link SemanticsOf#NON_IDEMPOTENT_ARE_YOU_SURE that cause a confirmation dialog to be displayed in the Wicket viewer.

typeOf

If the action returns a collection, then this hints as to the run-time type of the objects within that collection.

This is only provided as a fallback; usually the framework can infer the element type of the collection from the action method’s return type (eg if it returns Collection instead of Collection<Customer> )

fileAccept

For downloading Blob or Clob , optionally restrict the files accepted (eg .xslx ).

The value should be of the form "file_extension|audio/|video/|image/*|media_type".

Examples

For example:

public class ToDoItem {
    public static class CompletedEvent extends ActionDomainEvent<ToDoItem> { }
    @Action(
        commandPublishing=Publishing.ENABLED,
        commandExecuteIn=CommandExecuteIn.FOREGROUND,          (1)
        commandPersistence=CommandPersistence.NOT_PERSISTED,   (2)
        domainEvent=CompletedEvent.class,
        hidden = Where.NOWHERE,                                (3)
        executionPublishing = Publishing.ENABLED,
        semantics = SemanticsOf.IDEMPOTENT
    )
    public ToDoItem completed() { /* ... */ }
}
1 default value, so could be omitted
2 default value, so could be omitted
3 default value, so could be omitted

Usage Notes

Associating actions with properties and collections

The associateWith element allows an action to be associated with other properties or collections of the same domain object. The optional associateWithSequence element specifies the order of the action in the UI.

For example, an Order could have a collection of OrderItems, and might provide actions to add and remove items:

public class Order {

    @Getter @Setter
    @Collection
    private final SortedSet<OrderItem> items = ...

    @Action(
        associateWith="items",                      (1)
        associateWithSequence="1" )                 (2)
    public Order addItem(Product p, int quantity) {
        // ...
    }

    @Action(
        associateWith="items",                      (3)
        associateWithSequence="2" )                 (4)
    public Order removeItem(OrderItem item) {
        // ...
    }

    // ...
}
1 matches the name of the collection
2 first action in the list of all associated actions
3 matches the name of the collection
4 second action in the list of all associated actions

These actions - addItem() and removeItem() can be thought of as associated with with the items collection because that is the state that they primarily affect.

In the user interface associated actions are rendered close to the member to which they relate.

The same effect can be accomplished using @MemberOrder or with the .layout.xml file.

Action Semantics

The semantics() element describes whether the invocation modifies state of the system, and if so whether it does so idempotently. If the action invocation does not modify the state of the system, in other words is safe, then it also can beused to specify whether the results of the action can be cached automatically for the remainder of the request.

The semantics element was originally introduced for the RestfulObjects viewer in order that action invocations could be using the appropriate HTTP verb (GET, PUT and POST).

The table below summarizes the semantics:

Semantic Changes state Effect of multiple calls HTTP verb
(Restful Objects)

SAFE_AND_REQUEST_CACHEABLE

No

Will always return the same result each time invoked (within a given request scope)

GET

SAFE

No

Might result in different results each invocation

GET

IDEMPOTENT
IDEMPOTENT_ARE_YOU_SURE

Yes

Will make no further changes if called multiple times (eg sets a property or adds to a Set).
The "are you sure" variant requires that the user must explicitly confirm the action.

PUT

NON_IDEMPOTENT
NON_IDEMPOTENT_ARE_YOU_SURE

Yes

Might change the state of the system each time called (eg increments a counter or adds to a List).
The "are you sure" variant requires that the user must explicitly confirm the action.

POST

The actions' semantics are also used by the core runtime as part of the in-built concurrency checkng; invocation of a safe action (which includes request-cacheable) does not perform a concurrency check, whereas non-safe actions do perform a concurrency check.

For example:

public class Customer {

    @Action(semantics=SemanticsOf.SAFE_AND_REQUEST_CACHEABLE)
    public CreditRating checkCredit() {
        // ...
    }

    @Action(semantics=SemanticsOf.IDEMPOTENT)
    public void changeOfAddress(Address address) {
        // ...
    }

    @Action(semantics=SemanticsOf.NON_IDEMPOTENT)
    public Order placeNewOrder() {
        // ...
    }

    // ...
}

Actions that are safe and request-cacheable automatically use the QueryResultsCache service to cache the result of the method. Note though that the results of this caching will only be apparent if the action is invoked from another method using the WrapperFactory service.

Deployment modes

By default actions are available irrespective of the deployment mode. The restrictTo() element specifies whether the action should instead be restricted to only available in prototyping mode.

For example:

public class Customer {

    @Action
    public Order placeNewOrder() {
        // ...
    }
    @Action(semantics=SemanticsOf.SAFE)
    public List<Order> listRecentOrders() {
        // ...
    }

    @Action(restrictTo=RestrictTo.PROTOTYPING)      (1)
    public List<Order> listAllOrders() {
        // ...
    }
    ...
}
1 Only visible in prototype mode.

In this case the listing of all orders (in the listAllOrders() action) probably doesn’t make sense for production; there could be thousands or millions. However, it would be useful to disaply how for a test or demo system where there are only a handful of orders.

Domain events

Whenever a domain object (or list of domain objects) is to be rendered, the framework fires off multiple domain events for every property, collection and action of the domain object. In the cases of the domain object’s actions, the events that are fired are:

  • hide phase: to check that the action is visible (has not been hidden)

  • disable phase: to check that the action is usable (has not been disabled)

  • validate phase: to check that the action’s arguments are valid

  • pre-execute phase: before the invocation of the action

  • post-execute: after the invocation of the action

Subscribers subscribe through the EventBusService and can influence each of these phases.

By default the event raised is ActionDomainEvent.Default. For example:

public class ToDoItem {

    @Action()
    public ToDoItem completed() {
        // ...
    }
    ...
}

The domainEvent() element allows a custom subclass to be emitted allowing more precise subscriptions (to those subclasses) to be defined instead.

For example:

public class ToDoItem {
    public static class CompletedEvent extends ActionDomainEvent<ToDoItem> { }  (1)
    @Action(domainEvent=CompletedEvent.class)
    public ToDoItem completed() { /* ... */ }
}

The benefit is that subscribers can be more targeted as to the events that they subscribe to.

The framework provides a no-arg constructor and will initialize the domain event using (non-API) setters rather than through the constructor. This substantially reduces the boilerplate required in subclasses because no explicit constructor is required.

Subscribers

Subscribers (which must be domain services) subscribe to events posted through the EventBusService.

Subscribers can be either coarse-grained (if they subscribe to the top-level event type):

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

@Service
public class SomeSubscriber {
    @EventListener(ActionDomainEvent.class)
    public void on(ActionDomainEvent ev) {
        ...
    }
}

or can be fine-grained (by subscribing to specific event subtypes):

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

@Service
public class SomeSubscriber {
    @EventListener(ToDoItem.CompletedEvent.class)
    public void on(ToDoItem.CompletedEvent ev) {
        ...
    }
}

The subscriber’s method is called (up to) 5 times:

  • whether to veto visibility (hide)

  • whether to veto usability (disable)

  • whether to veto execution (validate)

  • steps to perform prior to the action being invoked

  • steps to perform after the action has been invoked

The subscriber can distinguish these by calling ev.getEventPhase(). Thus the general form is:

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

@Service
public class SomeSubscriber {

    @EventListener(ActionDomainEvent.class)
    public void on(ActionDomainEvent ev) {
        switch(ev.getEventPhase()) {

            case HIDE:                      (1)
                break;
            case DISABLE:                   (2)
                break;
            case VALIDATE:                  (3)
                break;

            case EXECUTING:
                break;
            case EXECUTED:
                break;
        }
    }
}
1 call ev.hide() or ev.veto("") to hide the action
2 call ev.disable("…​") or ev.veto("…​") to disable the action
3 call ev.invalidate("…​") or ev.veto("…​") if action arguments are invalid

It is also possible to abort the transaction during the executing or executed phases by throwing an exception. If the exception is a subtype of RecoverableException then the exception will be rendered as a user-friendly warning (eg Growl/toast) rather than an error.

Default, Doop and Noop events

If the domainEvent() element is not explicitly specified (is left as its default value, ActionDomainEvent.Default), then the framework will, by default, post an event.

If this is not required, then the isis.applib.annotation.action.domain-event.post-for-default configuration property can be set to "false"; this will disable posting.

On the other hand, if the domainEvent has been explicitly specified to some subclass, then an event will be posted. The framework provides ActionDomainEvent.Doop as such a subclass, so setting the domainEvent element to this class will ensure that the event to be posted, irrespective of the configuration property setting.

And, conversely, the framework also provides ActionDomainEvent.Noop; if domainEvent element is set to this class, then no event will be posted.

Class-level default

Sometimes a subscriber is interested in all of the actions of a given class, though not any individual action. A common use case is to hide or disable all actions for some particular object for some particular user group.

For this use, the default action domain event can be annotated using @DomainObject:

@DomainObject(
    actionDomainEvent=ToDoItem.ActionDomainEvent.class
)
public class ToDoItem {
    public static class ActionDomainEvent extends
        org.apache.isis.applib.events.domain.ActionDomainEvent<Object> { }
    // ...

    public void updateDescription(final String description) {
        this.description = description;
    }
}

Raising events programmatically

Normally events are only raised for interactions through the UI. However, events can be raised programmatically either by calling the EventBusService API directly, or by emulating the UI by wrapping the target object using the WrapperFactory domain service.

Execution Publishing

The executionPublishing() element determines whether and how an action invocation is published via the registered implementation of ExecutionSubscriber.

A common use case is to notify external "downstream" systems of changes in the state of the Apache Isis application.

The isis.applib.annotation.property.execution-publishing configuration property is used to determine the whether the action invocation is published:

  • all

    all action invocations are published

  • ignoreSafe (or ignoreQueryOnly)

    invocations of actions with safe (read-only) semantics are ignored, but actions which may modify data are not ignored

  • none

    no action invocations are published

If there is no configuration property in application.properties then publishing is automatically enabled.

This default can be overridden on an action-by-action basis; if executionPublishing() is set to ENABLED then the action invocation is published irrespective of the configured value; if set to DISABLED then the action invocation is not published, again irrespective of the configured value.

For example:

public class Order {
    @Action(executionPublishing=Publishing.ENABLED)    (1)
    public Invoice generateInvoice(...) {
        // ...
    }
}
1 because set to enabled, will be published irrespective of the configured value.

Command Processing

Every action invocation (and property edit for that matter) is normally reified into a concrete Command object, basically a wrapper around the CommandDto that also captures some timing metrics about the execution as well as the outcome.

The main uses cases are:

  • as a means to allow asynchronous child commands to be executed, using the WrapperFactory service;

  • as a means to audit (persist) commands, by implementing the CommandSubscriber SPI.

    The Command Log extension does provide such an implementation.

    Another option to achieve this is to use the ExecutionSubscriber SPI.
  • to replay commands onto a secondary system, for regression testing.

    This is implemented by the Command Replay extension, working in conjunction with the Command Log extension.

The commandPublishing() element can be used to explicitly enable or disable command publishing for the action invocation.

CommandDtoProcessor implementations

The commandDtoProcessor() element allows an implementation of CommandDtoProcessor to be specified. This interface has the following API:

public interface CommandDtoProcessor {
    CommandDto process(             (1)
            CommandDto dto);        (2)
}
1 The returned CommandDto. This will typically be the CommandDto passed in, but may be supplemented in some way.
2 The CommandDto obtained already from the Command.

This interface is used by the framework-provided implementations of ContentMappingService for the REST API, allowing Commands implementations that also implement CommandWithDto to be further customised as they are serialized out. The primary use case for this capability is in support of primary/secondary replication.

  • on the primary, Commands are serialized to XML. This includes the identity of the target object and the argument values of all parameters.

    Any Blobs and Clobs are deliberately excluded from this XML (they are instead stored as references). This is to prevent the storage requirements for Command from becoming excessive. A CommandDtoProcessor can be provided to re-attach blob information if required.

  • replaying Commands requires this missing parameter information to be reinstated. The CommandDtoProcessor therefore offers a hook to dynamically re-attach the missing Blob or Clob argument.

As a special case, returning null means that the command’s DTO is effectively excluded when retrieving the list of commands. If replicating from master to slave, this effectively allows certain commands to be ignored. The CommandDtoProcessor.Null class provides a convenience implementation for this requirement.

If commandDtoProcessor() is specified, then commandPublishing() is assumed to be ENABLED.

Example

Consider the following method:

@Action(
    domainEvent = IncomingDocumentRepository.UploadDomainEvent.class,
    commandDtoProcessor = DeriveBlobArg0FromReturnedDocument.class
)
public Document upload(final Blob blob) {
    final String name = blob.getName();
    final DocumentType type = DocumentTypeData.INCOMING.findUsing(documentTypeRepository);
    final ApplicationUser me = meService.me();
    String atPath = me != null ? me.getAtPath() : null;
    if (atPath == null) {
        atPath = "/";
    }
    return incomingDocumentRepository.upsertAndArchive(type, atPath, name, blob);
}

The Blob argument will not be persisted in the memento of the Command, but the information is implicitly available in the Document that is returned by the action. The DeriveBlobArg0FromReturnedDocument processor retrieves this information and dynamically adds:

public class DeriveBlobArg0FromReturnedDocument
        extends CommandDtoProcessorForActionAbstract {

    @Override
    public CommandDto process(Command command, CommandDto commandDto) {
        final Bookmark result = commandWithDto.getResult();
        if(result == null) {
            return commandDto;
        }
        try {
            final Document document = bookmarkService.lookup(result, Document.class);
            if (document != null) {
                ParamDto paramDto = getParamDto(commandDto, 0);
                CommonDtoUtils.setValueOn(paramDto, ValueType.BLOB, document.getBlob(), bookmarkService);
            }
        } catch(Exception ex) {
            return commandDto;
        }
        return commandDto;
    }
    @Inject
    BookmarkService bookmarkService;
}

Null implementation

The null implementation can be used to simply indicate that no DTO should be returned for a Command. The effect is to ignore it for replay purposes:

pubc interface CommandDtoProcessor {
    ...
    class Null implements CommandDtoProcessor {
        public CommandDto process(Command command, CommandDto commandDto) {
            return null;
        }
    }
}

Collection type

The typeOf() element specifies the expected type of an element returned by the action (returning a collection), when for whatever reason the type cannot be inferred from the generic type, or to provide a hint about the actual run-time (as opposed to compile-time) type.

For example:

public void AccountService {

    @Action(typeOf=Customer.class)
    public List errantAccounts() {
        return customers.allNewCustomers();
    }
    ...

    @Inject CustomerRepository customers;
}

In general we recommend that you use generics instead, eg List<Customer>.

See also