@Action
Groups together all domain-specific metadata for an invokable action on a domain object or domain service.
API
@interface Action {
String choicesFrom() default ""; (1)
Class<? extends CommandDtoProcessor> commandDtoProcessor() default CommandDtoProcessor.class; (2)
Publishing commandPublishing() default Publishing.NOT_SPECIFIED; (3)
Class<? extends ActionDomainEvent<?>> domainEvent() default ActionDomainEvent.Default.class; (4)
Publishing executionPublishing() default Publishing.NOT_SPECIFIED; (5)
RestrictTo restrictTo() default RestrictTo.NOT_SPECIFIED; (6)
SemanticsOf semantics() default SemanticsOf.NOT_SPECIFIED; (7)
Class<?> typeOf() default // represents unspecified
void.class; (8)
String fileAccept() default ""; (9)
}
1 | choicesFrom
References a collection of a certain element type (by its member-id), for an action with scalar or collection parameters of that element type; the action’s choices will be automatically populated from checkboxes rendered in the collection (multi-select). |
2 | commandDtoProcessor
The CommandDtoProcessor to process this command’s DTO. |
3 | commandPublishing
Whether action invocations, captured as Command s, should be published to CommandSubscriber s. |
4 | domainEvent
Indicates that an invocation of the action should be posted to the org.apache.causeway.applib.services.eventbus.EventBusService using a custom (subclass of) ActionDomainEvent . |
5 | executionPublishing
Whether Execution s (triggered by action invocations), should be published to ExecutionSubscriber s. |
6 | restrictTo
Whether the action is restricted to prototyping, or whether it is available also in production mode. |
7 | 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 . |
8 | typeOf
If the action returns a collection, then this hints as to the run-time type of the objects within that collection. |
9 | fileAccept |
Members
choicesFrom
References a collection of a certain element type (by its member-id), for an action with scalar or collection parameters of that element type; the action’s choices will be automatically populated from checkboxes rendered in the collection (multi-select).
This will also result in the action being rendered near to the collection, similar to the way in which ActionLayout#associateWith() does.
If there are multiple actions associated with a collection, either by way of Action#choicesFrom() or using ActionLayout#associateWith() , then their order in the UI can be influenced using ActionLayout#sequence() .
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.causeway.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.
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>
)
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 sequence
element specifies the order of the action in the UI.
For example, an Order
could have a collection of OrderItem
s, and might provide actions to add and remove items:
public class Order {
@Getter @Setter
@Collection
private final SortedSet<OrderItem> items = ...
@Action(associateWith="items") (1)
@ActionLayout(sequence="1" ) (2)
public Order addItem(Product p, int quantity) {
// ...
}
@Action(associateWith="items") (3)
@ActionLayout(sequence="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 the |
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) |
---|---|---|---|
|
No |
Will always return the same result each time invoked (within a given request scope) |
|
|
No |
Might result in different results each invocation |
|
|
Yes |
Will make no further changes if called multiple times (eg sets a property or adds to a |
|
|
Yes |
Might change the state of the system each time called (eg increments a counter or adds to a |
|
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 causeway.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.causeway.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 Causeway application.
The causeway.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
(orignoreQueryOnly
)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.
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
Blob
s andClob
s are deliberately excluded from this XML (they are instead stored as references). This is to prevent the storage requirements for Command from becoming excessive. ACommandDtoProcessor
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 missingBlob
orClob
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 |
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>
.
Related SPIs
-
for actions where command publishing (as per @Action#commandPublishing()) is enabled
-
Command Log extension
provides an implementation that simply logs actions using the logging library
-
-
for actions where execution publishing (as per @Action#executionPublishing) is enabled
See also
-
which broadcasts domain events as per @Action#domainEvent()